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.
4374 lines
128 KiB
4374 lines
128 KiB
// -*- mode: C++; c-file-style: "gnu" -*-
|
|
// kmmessage.cpp
|
|
|
|
// if you do not want GUI elements in here then set ALLOW_GUI to 0.
|
|
#include <config.h>
|
|
// needed temporarily until KMime is replacing the partNode helper class:
|
|
#include "partNode.h"
|
|
|
|
|
|
#define ALLOW_GUI 1
|
|
#include "kmkernel.h"
|
|
#include "kmmessage.h"
|
|
#include "mailinglist-magic.h"
|
|
#include "messageproperty.h"
|
|
using KMail::MessageProperty;
|
|
#include "objecttreeparser.h"
|
|
using KMail::ObjectTreeParser;
|
|
#include "kmfolderindex.h"
|
|
#include "undostack.h"
|
|
#include "kmversion.h"
|
|
#include "headerstrategy.h"
|
|
#include "globalsettings.h"
|
|
using KMail::HeaderStrategy;
|
|
#include "kmaddrbook.h"
|
|
#include "kcursorsaver.h"
|
|
#include "templateparser.h"
|
|
|
|
#include <libkpimidentities/identity.h>
|
|
#include <libkpimidentities/identitymanager.h>
|
|
#include <libemailfunctions/email.h>
|
|
|
|
#include <kasciistringtools.h>
|
|
|
|
#include <kpgpblock.h>
|
|
#include <kaddrbook.h>
|
|
|
|
#include <kapplication.h>
|
|
#include <kglobalsettings.h>
|
|
#include <kdebug.h>
|
|
#include <kconfig.h>
|
|
#include <khtml_part.h>
|
|
#include <kuser.h>
|
|
#include <kidna.h>
|
|
#include <kasciistricmp.h>
|
|
|
|
#include <qcursor.h>
|
|
#include <qtextcodec.h>
|
|
#include <qmessagebox.h>
|
|
#include <kmime_util.h>
|
|
#include <kmime_charfreq.h>
|
|
|
|
#include <kmime_header_parsing.h>
|
|
using KMime::HeaderParsing::parseAddressList;
|
|
using namespace KMime::Types;
|
|
|
|
#include <mimelib/body.h>
|
|
#include <mimelib/field.h>
|
|
#include <mimelib/mimepp.h>
|
|
#include <mimelib/string.h>
|
|
#include <assert.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
#include <klocale.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include "util.h"
|
|
|
|
#if ALLOW_GUI
|
|
#include <kmessagebox.h>
|
|
#endif
|
|
|
|
using namespace KMime;
|
|
|
|
static DwString emptyString("");
|
|
|
|
// Values that are set from the config file with KMMessage::readConfig()
|
|
static QString sReplyLanguage, sReplyStr, sReplyAllStr, sIndentPrefixStr;
|
|
static bool sSmartQuote,
|
|
sWordWrap;
|
|
static int sWrapCol;
|
|
static QStringList sPrefCharsets;
|
|
|
|
QString KMMessage::sForwardStr;
|
|
const HeaderStrategy * KMMessage::sHeaderStrategy = HeaderStrategy::rich();
|
|
//helper
|
|
static void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart );
|
|
|
|
QValueList<KMMessage*> KMMessage::sPendingDeletes;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
KMMessage::KMMessage(DwMessage* aMsg)
|
|
: KMMsgBase()
|
|
{
|
|
init( aMsg );
|
|
// aMsg might need assembly
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
KMMessage::KMMessage(KMFolder* parent): KMMsgBase(parent)
|
|
{
|
|
init();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
KMMessage::KMMessage(KMMsgInfo& msgInfo): KMMsgBase()
|
|
{
|
|
init();
|
|
// now overwrite a few from the msgInfo
|
|
mMsgSize = msgInfo.msgSize();
|
|
mFolderOffset = msgInfo.folderOffset();
|
|
mStatus = msgInfo.status();
|
|
mEncryptionState = msgInfo.encryptionState();
|
|
mSignatureState = msgInfo.signatureState();
|
|
mMDNSentState = msgInfo.mdnSentState();
|
|
mDate = msgInfo.date();
|
|
mFileName = msgInfo.fileName();
|
|
KMMsgBase::assign(&msgInfo);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
KMMessage::KMMessage(const KMMessage& other) :
|
|
KMMsgBase( other ),
|
|
ISubject(),
|
|
mMsg(0)
|
|
{
|
|
init(); // to be safe
|
|
assign( other );
|
|
}
|
|
|
|
void KMMessage::init( DwMessage* aMsg )
|
|
{
|
|
mNeedsAssembly = false;
|
|
if ( aMsg ) {
|
|
mMsg = aMsg;
|
|
} else {
|
|
mMsg = new DwMessage;
|
|
}
|
|
mOverrideCodec = 0;
|
|
mDecodeHTML = false;
|
|
mComplete = true;
|
|
mReadyToShow = true;
|
|
mMsgSize = 0;
|
|
mMsgLength = 0;
|
|
mFolderOffset = 0;
|
|
mStatus = KMMsgStatusNew;
|
|
mEncryptionState = KMMsgEncryptionStateUnknown;
|
|
mSignatureState = KMMsgSignatureStateUnknown;
|
|
mMDNSentState = KMMsgMDNStateUnknown;
|
|
mDate = 0;
|
|
mUnencryptedMsg = 0;
|
|
mLastUpdated = 0;
|
|
mCursorPos = 0;
|
|
mMsgInfo = 0;
|
|
mIsParsed = false;
|
|
}
|
|
|
|
void KMMessage::assign( const KMMessage& other )
|
|
{
|
|
MessageProperty::forget( this );
|
|
delete mMsg;
|
|
delete mUnencryptedMsg;
|
|
|
|
mNeedsAssembly = true;//other.mNeedsAssembly;
|
|
if( other.mMsg )
|
|
mMsg = new DwMessage( *(other.mMsg) );
|
|
else
|
|
mMsg = 0;
|
|
mOverrideCodec = other.mOverrideCodec;
|
|
mDecodeHTML = other.mDecodeHTML;
|
|
mMsgSize = other.mMsgSize;
|
|
mMsgLength = other.mMsgLength;
|
|
mFolderOffset = other.mFolderOffset;
|
|
mStatus = other.mStatus;
|
|
mEncryptionState = other.mEncryptionState;
|
|
mSignatureState = other.mSignatureState;
|
|
mMDNSentState = other.mMDNSentState;
|
|
mIsParsed = other.mIsParsed;
|
|
mDate = other.mDate;
|
|
if( other.hasUnencryptedMsg() )
|
|
mUnencryptedMsg = new KMMessage( *other.unencryptedMsg() );
|
|
else
|
|
mUnencryptedMsg = 0;
|
|
setDrafts( other.drafts() );
|
|
setTemplates( other.templates() );
|
|
//mFileName = ""; // we might not want to copy the other messages filename (?)
|
|
//KMMsgBase::assign( &other );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
KMMessage::~KMMessage()
|
|
{
|
|
delete mMsgInfo;
|
|
delete mMsg;
|
|
kmkernel->undoStack()->msgDestroyed( this );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setReferences(const QCString& aStr)
|
|
{
|
|
if (!aStr) return;
|
|
mMsg->Headers().References().FromString(aStr);
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QCString KMMessage::id() const
|
|
{
|
|
DwHeaders& header = mMsg->Headers();
|
|
if (header.HasMessageId())
|
|
return KMail::Util::CString( header.MessageId().AsString() );
|
|
else
|
|
return "";
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//WARNING: This method updates the memory resident cache of serial numbers
|
|
//WARNING: held in MessageProperty, but it does not update the persistent
|
|
//WARNING: store of serial numbers on the file system that is managed by
|
|
//WARNING: KMMsgDict
|
|
void KMMessage::setMsgSerNum(unsigned long newMsgSerNum)
|
|
{
|
|
MessageProperty::setSerialCache( this, newMsgSerNum );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMessage::isMessage() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMessage::transferInProgress() const
|
|
{
|
|
return MessageProperty::transferInProgress( getMsgSerNum() );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setTransferInProgress(bool value, bool force)
|
|
{
|
|
MessageProperty::setTransferInProgress( getMsgSerNum(), value, force );
|
|
if ( !transferInProgress() && sPendingDeletes.contains( this ) ) {
|
|
sPendingDeletes.remove( this );
|
|
if ( parent() ) {
|
|
int idx = parent()->find( this );
|
|
if ( idx > 0 ) {
|
|
parent()->removeMsg( idx );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool KMMessage::isUrgent() const {
|
|
return headerField( "Priority" ).contains( "urgent", false )
|
|
|| headerField( "X-Priority" ).startsWith( "2" );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setUnencryptedMsg( KMMessage* unencrypted )
|
|
{
|
|
delete mUnencryptedMsg;
|
|
mUnencryptedMsg = unencrypted;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//FIXME: move to libemailfunctions
|
|
KPIM::EmailParseResult KMMessage::isValidEmailAddressList( const QString& aStr,
|
|
QString& brokenAddress )
|
|
{
|
|
if ( aStr.isEmpty() ) {
|
|
return KPIM::AddressEmpty;
|
|
}
|
|
|
|
QStringList list = KPIM::splitEmailAddrList( aStr );
|
|
for( QStringList::const_iterator it = list.begin(); it != list.end(); ++it ) {
|
|
KPIM::EmailParseResult errorCode = KPIM::isValidEmailAddress( *it );
|
|
if ( errorCode != KPIM::AddressOk ) {
|
|
brokenAddress = ( *it );
|
|
return errorCode;
|
|
}
|
|
}
|
|
return KPIM::AddressOk;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
const DwString& KMMessage::asDwString() const
|
|
{
|
|
if (mNeedsAssembly)
|
|
{
|
|
mNeedsAssembly = false;
|
|
mMsg->Assemble();
|
|
}
|
|
return mMsg->AsString();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
const DwMessage* KMMessage::asDwMessage()
|
|
{
|
|
if (mNeedsAssembly)
|
|
{
|
|
mNeedsAssembly = false;
|
|
mMsg->Assemble();
|
|
}
|
|
return mMsg;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QCString KMMessage::asString() const {
|
|
return KMail::Util::CString( asDwString() );
|
|
}
|
|
|
|
|
|
QByteArray KMMessage::asSendableString() const
|
|
{
|
|
KMMessage msg( new DwMessage( *this->mMsg ) );
|
|
msg.removePrivateHeaderFields();
|
|
msg.removeHeaderField("Bcc");
|
|
return KMail::Util::ByteArray( msg.asDwString() ); // and another copy again!
|
|
}
|
|
|
|
QCString KMMessage::headerAsSendableString() const
|
|
{
|
|
KMMessage msg( new DwMessage( *this->mMsg ) );
|
|
msg.removePrivateHeaderFields();
|
|
msg.removeHeaderField("Bcc");
|
|
return msg.headerAsString().latin1();
|
|
}
|
|
|
|
void KMMessage::removePrivateHeaderFields() {
|
|
removeHeaderField("Status");
|
|
removeHeaderField("X-Status");
|
|
removeHeaderField("X-KMail-EncryptionState");
|
|
removeHeaderField("X-KMail-SignatureState");
|
|
removeHeaderField("X-KMail-MDN-Sent");
|
|
removeHeaderField("X-KMail-Transport");
|
|
removeHeaderField("X-KMail-Identity");
|
|
removeHeaderField("X-KMail-Fcc");
|
|
removeHeaderField("X-KMail-Redirect-From");
|
|
removeHeaderField("X-KMail-Link-Message");
|
|
removeHeaderField("X-KMail-Link-Type");
|
|
removeHeaderField( "X-KMail-Markup" );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setStatusFields()
|
|
{
|
|
char str[2] = { 0, 0 };
|
|
|
|
setHeaderField("Status", status() & KMMsgStatusNew ? "R" : "RO");
|
|
setHeaderField("X-Status", statusToStr(status()));
|
|
|
|
str[0] = (char)encryptionState();
|
|
setHeaderField("X-KMail-EncryptionState", str);
|
|
|
|
str[0] = (char)signatureState();
|
|
//kdDebug(5006) << "Setting SignatureState header field to " << str[0] << endl;
|
|
setHeaderField("X-KMail-SignatureState", str);
|
|
|
|
str[0] = static_cast<char>( mdnSentState() );
|
|
setHeaderField("X-KMail-MDN-Sent", str);
|
|
|
|
// We better do the assembling ourselves now to prevent the
|
|
// mimelib from changing the message *body*. (khz, 10.8.2002)
|
|
mNeedsAssembly = false;
|
|
mMsg->Headers().Assemble();
|
|
mMsg->Assemble( mMsg->Headers(),
|
|
mMsg->Body() );
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
QString KMMessage::headerAsString() const
|
|
{
|
|
DwHeaders& header = mMsg->Headers();
|
|
header.Assemble();
|
|
if ( header.AsString().empty() )
|
|
return QString::null;
|
|
return QString::fromLatin1( header.AsString().c_str() );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
DwMediaType& KMMessage::dwContentType()
|
|
{
|
|
return mMsg->Headers().ContentType();
|
|
}
|
|
|
|
void KMMessage::fromByteArray( const QByteArray & ba, bool setStatus ) {
|
|
return fromDwString( DwString( ba.data(), ba.size() ), setStatus );
|
|
}
|
|
|
|
void KMMessage::fromString( const QCString & str, bool aSetStatus ) {
|
|
return fromDwString( KMail::Util::dwString( str ), aSetStatus );
|
|
}
|
|
|
|
void KMMessage::fromDwString(const DwString& str, bool aSetStatus)
|
|
{
|
|
delete mMsg;
|
|
mMsg = new DwMessage;
|
|
mMsg->FromString( str );
|
|
mMsg->Parse();
|
|
|
|
if (aSetStatus) {
|
|
setStatus(headerField("Status").latin1(), headerField("X-Status").latin1());
|
|
setEncryptionStateChar( headerField("X-KMail-EncryptionState").at(0) );
|
|
setSignatureStateChar( headerField("X-KMail-SignatureState").at(0) );
|
|
setMDNSentState( static_cast<KMMsgMDNSentState>( headerField("X-KMail-MDN-Sent").at(0).latin1() ) );
|
|
}
|
|
if (attachmentState() == KMMsgAttachmentUnknown && readyToShow())
|
|
updateAttachmentState();
|
|
|
|
mNeedsAssembly = false;
|
|
mDate = date();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::formatString(const QString& aStr) const
|
|
{
|
|
QString result, str;
|
|
QChar ch;
|
|
uint j;
|
|
|
|
if (aStr.isEmpty())
|
|
return aStr;
|
|
|
|
unsigned int strLength(aStr.length());
|
|
for (uint i=0; i<strLength;) {
|
|
ch = aStr[i++];
|
|
if (ch == '%') {
|
|
ch = aStr[i++];
|
|
switch ((char)ch) {
|
|
case 'D':
|
|
/* I'm not too sure about this change. Is it not possible
|
|
to have a long form of the date used? I don't
|
|
like this change to a short XX/XX/YY date format.
|
|
At least not for the default. -sanders */
|
|
result += KMime::DateFormatter::formatDate( KMime::DateFormatter::Localized,
|
|
date(), sReplyLanguage, false );
|
|
break;
|
|
case 'e':
|
|
result += from();
|
|
break;
|
|
case 'F':
|
|
result += fromStrip();
|
|
break;
|
|
case 'f':
|
|
{
|
|
str = fromStrip();
|
|
|
|
for (j=0; str[j]>' '; j++)
|
|
;
|
|
unsigned int strLength(str.length());
|
|
for (; j < strLength && str[j] <= ' '; j++)
|
|
;
|
|
result += str[0];
|
|
if (str[j]>' ')
|
|
result += str[j];
|
|
else
|
|
if (str[1]>' ')
|
|
result += str[1];
|
|
}
|
|
break;
|
|
case 'T':
|
|
result += toStrip();
|
|
break;
|
|
case 't':
|
|
result += to();
|
|
break;
|
|
case 'C':
|
|
result += ccStrip();
|
|
break;
|
|
case 'c':
|
|
result += cc();
|
|
break;
|
|
case 'S':
|
|
result += subject();
|
|
break;
|
|
case '_':
|
|
result += ' ';
|
|
break;
|
|
case 'L':
|
|
result += "\n";
|
|
break;
|
|
case '%':
|
|
result += '%';
|
|
break;
|
|
default:
|
|
result += '%';
|
|
result += ch;
|
|
break;
|
|
}
|
|
} else
|
|
result += ch;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void removeTrailingSpace( QString &line )
|
|
{
|
|
int i = line.length()-1;
|
|
while( (i >= 0) && ((line[i] == ' ') || (line[i] == '\t')))
|
|
i--;
|
|
line.truncate( i+1);
|
|
}
|
|
|
|
static QString splitLine( QString &line)
|
|
{
|
|
removeTrailingSpace( line );
|
|
int i = 0;
|
|
int j = -1;
|
|
int l = line.length();
|
|
|
|
// TODO: Replace tabs with spaces first.
|
|
|
|
while(i < l)
|
|
{
|
|
QChar c = line[i];
|
|
if ((c == '>') || (c == ':') || (c == '|'))
|
|
j = i+1;
|
|
else if ((c != ' ') && (c != '\t'))
|
|
break;
|
|
i++;
|
|
}
|
|
|
|
if ( j <= 0 )
|
|
{
|
|
return "";
|
|
}
|
|
if ( i == l )
|
|
{
|
|
QString result = line.left(j);
|
|
line = QString::null;
|
|
return result;
|
|
}
|
|
|
|
QString result = line.left(j);
|
|
line = line.mid(j);
|
|
return result;
|
|
}
|
|
|
|
static QString flowText(QString &text, const QString& indent, int maxLength)
|
|
{
|
|
maxLength--;
|
|
if (text.isEmpty())
|
|
{
|
|
return indent+"<NULL>\n";
|
|
}
|
|
QString result;
|
|
while (1)
|
|
{
|
|
int i;
|
|
if ((int) text.length() > maxLength)
|
|
{
|
|
i = maxLength;
|
|
while( (i >= 0) && (text[i] != ' '))
|
|
i--;
|
|
if (i <= 0)
|
|
{
|
|
// Couldn't break before maxLength.
|
|
i = maxLength;
|
|
// while( (i < (int) text.length()) && (text[i] != ' '))
|
|
// i++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
i = text.length();
|
|
}
|
|
|
|
QString line = text.left(i);
|
|
if (i < (int) text.length())
|
|
text = text.mid(i);
|
|
else
|
|
text = QString::null;
|
|
|
|
result += indent + line + '\n';
|
|
|
|
if (text.isEmpty())
|
|
return result;
|
|
}
|
|
}
|
|
|
|
static bool flushPart(QString &msg, QStringList &part,
|
|
const QString &indent, int maxLength)
|
|
{
|
|
maxLength -= indent.length();
|
|
if (maxLength < 20) maxLength = 20;
|
|
|
|
// Remove empty lines at end of quote
|
|
while ((part.begin() != part.end()) && part.last().isEmpty())
|
|
{
|
|
part.remove(part.fromLast());
|
|
}
|
|
|
|
QString text;
|
|
for(QStringList::Iterator it2 = part.begin();
|
|
it2 != part.end();
|
|
it2++)
|
|
{
|
|
QString line = (*it2);
|
|
|
|
if (line.isEmpty())
|
|
{
|
|
if (!text.isEmpty())
|
|
msg += flowText(text, indent, maxLength);
|
|
msg += indent + '\n';
|
|
}
|
|
else
|
|
{
|
|
if (text.isEmpty())
|
|
text = line;
|
|
else
|
|
text += ' '+line.stripWhiteSpace();
|
|
|
|
if (((int) text.length() < maxLength) || ((int) line.length() < (maxLength-10)))
|
|
msg += flowText(text, indent, maxLength);
|
|
}
|
|
}
|
|
if (!text.isEmpty())
|
|
msg += flowText(text, indent, maxLength);
|
|
|
|
bool appendEmptyLine = true;
|
|
if (!part.count())
|
|
appendEmptyLine = false;
|
|
|
|
part.clear();
|
|
return appendEmptyLine;
|
|
}
|
|
|
|
static QString stripSignature( const QString & msg, bool clearSigned ) {
|
|
if ( clearSigned )
|
|
return msg.left( msg.findRev( QRegExp( "\n--\\s?\n" ) ) );
|
|
else
|
|
return msg.left( msg.findRev( "\n-- \n" ) );
|
|
}
|
|
|
|
QString KMMessage::smartQuote( const QString & msg, int maxLineLength )
|
|
{
|
|
QStringList part;
|
|
QString oldIndent;
|
|
bool firstPart = true;
|
|
|
|
|
|
const QStringList lines = QStringList::split('\n', msg, true);
|
|
|
|
QString result;
|
|
for(QStringList::const_iterator it = lines.begin();
|
|
it != lines.end();
|
|
++it)
|
|
{
|
|
QString line = *it;
|
|
|
|
const QString indent = splitLine( line );
|
|
|
|
if ( line.isEmpty())
|
|
{
|
|
if (!firstPart)
|
|
part.append(QString::null);
|
|
continue;
|
|
};
|
|
|
|
if (firstPart)
|
|
{
|
|
oldIndent = indent;
|
|
firstPart = false;
|
|
}
|
|
|
|
if (oldIndent != indent)
|
|
{
|
|
QString fromLine;
|
|
// Search if the last non-blank line could be "From" line
|
|
if (part.count() && (oldIndent.length() < indent.length()))
|
|
{
|
|
QStringList::Iterator it2 = part.fromLast();
|
|
while( (it2 != part.end()) && (*it2).isEmpty())
|
|
--it2;
|
|
|
|
if ((it2 != part.end()) && ((*it2).endsWith(":")))
|
|
{
|
|
fromLine = oldIndent + (*it2) + '\n';
|
|
part.remove(it2);
|
|
}
|
|
}
|
|
if (flushPart( result, part, oldIndent, maxLineLength))
|
|
{
|
|
if (oldIndent.length() > indent.length())
|
|
result += indent + '\n';
|
|
else
|
|
result += oldIndent + '\n';
|
|
}
|
|
if (!fromLine.isEmpty())
|
|
{
|
|
result += fromLine;
|
|
}
|
|
oldIndent = indent;
|
|
}
|
|
part.append(line);
|
|
}
|
|
flushPart( result, part, oldIndent, maxLineLength);
|
|
return result;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::parseTextStringFromDwPart( partNode * root,
|
|
QCString& parsedString,
|
|
const QTextCodec*& codec,
|
|
bool& isHTML ) const
|
|
{
|
|
if ( !root ) return;
|
|
|
|
isHTML = false;
|
|
// initialy parse the complete message to decrypt any encrypted parts
|
|
{
|
|
ObjectTreeParser otp( 0, 0, true, false, true );
|
|
otp.parseObjectTree( root );
|
|
}
|
|
partNode * curNode = root->findType( DwMime::kTypeText,
|
|
DwMime::kSubtypeUnknown,
|
|
true,
|
|
false );
|
|
kdDebug(5006) << "\n\n======= KMMessage::parseTextStringFromDwPart() - "
|
|
<< ( curNode ? "text part found!\n" : "sorry, no text node!\n" ) << endl;
|
|
if( curNode ) {
|
|
isHTML = DwMime::kSubtypeHtml == curNode->subType();
|
|
// now parse the TEXT message part we want to quote
|
|
ObjectTreeParser otp( 0, 0, true, false, true );
|
|
otp.parseObjectTree( curNode );
|
|
parsedString = otp.rawReplyString();
|
|
codec = curNode->msgPart().codec();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
QString KMMessage::asPlainText( bool aStripSignature, bool allowDecryption ) const {
|
|
QCString parsedString;
|
|
bool isHTML = false;
|
|
const QTextCodec * codec = 0;
|
|
|
|
partNode * root = partNode::fromMessage( this );
|
|
if ( !root ) return QString::null;
|
|
parseTextStringFromDwPart( root, parsedString, codec, isHTML );
|
|
delete root;
|
|
|
|
if ( mOverrideCodec || !codec )
|
|
codec = this->codec();
|
|
|
|
if ( parsedString.isEmpty() )
|
|
return QString::null;
|
|
|
|
bool clearSigned = false;
|
|
QString result;
|
|
|
|
// decrypt
|
|
if ( allowDecryption ) {
|
|
QPtrList<Kpgp::Block> pgpBlocks;
|
|
QStrList nonPgpBlocks;
|
|
if ( Kpgp::Module::prepareMessageForDecryption( parsedString,
|
|
pgpBlocks,
|
|
nonPgpBlocks ) ) {
|
|
// Only decrypt/strip off the signature if there is only one OpenPGP
|
|
// block in the message
|
|
if ( pgpBlocks.count() == 1 ) {
|
|
Kpgp::Block * block = pgpBlocks.first();
|
|
if ( block->type() == Kpgp::PgpMessageBlock ||
|
|
block->type() == Kpgp::ClearsignedBlock ) {
|
|
if ( block->type() == Kpgp::PgpMessageBlock ) {
|
|
// try to decrypt this OpenPGP block
|
|
block->decrypt();
|
|
} else {
|
|
// strip off the signature
|
|
block->verify();
|
|
clearSigned = true;
|
|
}
|
|
|
|
result = codec->toUnicode( nonPgpBlocks.first() )
|
|
+ codec->toUnicode( block->text() )
|
|
+ codec->toUnicode( nonPgpBlocks.last() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( result.isEmpty() ) {
|
|
result = codec->toUnicode( parsedString );
|
|
if ( result.isEmpty() )
|
|
return result;
|
|
}
|
|
|
|
// html -> plaintext conversion, if necessary:
|
|
if ( isHTML && mDecodeHTML ) {
|
|
KHTMLPart htmlPart;
|
|
htmlPart.setOnlyLocalReferences( true );
|
|
htmlPart.setMetaRefreshEnabled( false );
|
|
htmlPart.setPluginsEnabled( false );
|
|
htmlPart.setJScriptEnabled( false );
|
|
htmlPart.setJavaEnabled( false );
|
|
htmlPart.begin();
|
|
htmlPart.write( result );
|
|
htmlPart.end();
|
|
htmlPart.selectAll();
|
|
result = htmlPart.selectedText();
|
|
}
|
|
|
|
// strip the signature (footer):
|
|
if ( aStripSignature )
|
|
return stripSignature( result, clearSigned );
|
|
else
|
|
return result;
|
|
}
|
|
|
|
QString KMMessage::asQuotedString( const QString& aHeaderStr,
|
|
const QString& aIndentStr,
|
|
const QString& selection /* = QString::null */,
|
|
bool aStripSignature /* = true */,
|
|
bool allowDecryption /* = true */) const
|
|
{
|
|
QString content = selection.isEmpty() ?
|
|
asPlainText( aStripSignature, allowDecryption ) : selection ;
|
|
|
|
// Remove blank lines at the beginning:
|
|
const int firstNonWS = content.find( QRegExp( "\\S" ) );
|
|
const int lineStart = content.findRev( '\n', firstNonWS );
|
|
if ( lineStart >= 0 )
|
|
content.remove( 0, static_cast<unsigned int>( lineStart ) );
|
|
|
|
const QString indentStr = formatString( aIndentStr );
|
|
|
|
content.replace( '\n', '\n' + indentStr );
|
|
content.prepend( indentStr );
|
|
content += '\n';
|
|
|
|
const QString headerStr = formatString( aHeaderStr );
|
|
if ( sSmartQuote && sWordWrap )
|
|
return headerStr + smartQuote( content, sWrapCol );
|
|
return headerStr + content;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
KMMessage* KMMessage::createReply( KMail::ReplyStrategy replyStrategy,
|
|
QString selection /* = QString::null */,
|
|
bool noQuote /* = false */,
|
|
bool allowDecryption /* = true */,
|
|
bool selectionIsBody /* = false */,
|
|
const QString &tmpl /* = QString::null */ )
|
|
{
|
|
KMMessage* msg = new KMMessage;
|
|
QString str, replyStr, mailingListStr, replyToStr, toStr;
|
|
QStringList mailingListAddresses;
|
|
QCString refStr, headerName;
|
|
bool replyAll = true;
|
|
|
|
msg->initFromMessage(this);
|
|
|
|
MailingList::name(this, headerName, mailingListStr);
|
|
replyToStr = replyTo();
|
|
|
|
msg->setCharset("utf-8");
|
|
|
|
// determine the mailing list posting address
|
|
if ( parent() && parent()->isMailingListEnabled() &&
|
|
!parent()->mailingListPostAddress().isEmpty() ) {
|
|
mailingListAddresses << parent()->mailingListPostAddress();
|
|
}
|
|
if ( headerField("List-Post").find( "mailto:", 0, false ) != -1 ) {
|
|
QString listPost = headerField("List-Post");
|
|
QRegExp rx( "<mailto:([^@>]+)@([^>]+)>", false );
|
|
if ( rx.search( listPost, 0 ) != -1 ) // matched
|
|
mailingListAddresses << rx.cap(1) + '@' + rx.cap(2);
|
|
}
|
|
|
|
// use the "On ... Joe User wrote:" header by default
|
|
replyStr = sReplyAllStr;
|
|
|
|
switch( replyStrategy ) {
|
|
case KMail::ReplySmart : {
|
|
if ( !headerField( "Mail-Followup-To" ).isEmpty() ) {
|
|
toStr = headerField( "Mail-Followup-To" );
|
|
}
|
|
else if ( !replyToStr.isEmpty() ) {
|
|
// assume a Reply-To header mangling mailing list
|
|
toStr = replyToStr;
|
|
}
|
|
else if ( !mailingListAddresses.isEmpty() ) {
|
|
toStr = mailingListAddresses[0];
|
|
}
|
|
else {
|
|
// doesn't seem to be a mailing list, reply to From: address
|
|
toStr = from();
|
|
replyStr = sReplyStr; // reply to author, so use "On ... you wrote:"
|
|
replyAll = false;
|
|
}
|
|
// strip all my addresses from the list of recipients
|
|
QStringList recipients = KPIM::splitEmailAddrList( toStr );
|
|
toStr = stripMyAddressesFromAddressList( recipients ).join(", ");
|
|
// ... unless the list contains only my addresses (reply to self)
|
|
if ( toStr.isEmpty() && !recipients.isEmpty() )
|
|
toStr = recipients[0];
|
|
|
|
break;
|
|
}
|
|
case KMail::ReplyList : {
|
|
if ( !headerField( "Mail-Followup-To" ).isEmpty() ) {
|
|
toStr = headerField( "Mail-Followup-To" );
|
|
}
|
|
else if ( !mailingListAddresses.isEmpty() ) {
|
|
toStr = mailingListAddresses[0];
|
|
}
|
|
else if ( !replyToStr.isEmpty() ) {
|
|
// assume a Reply-To header mangling mailing list
|
|
toStr = replyToStr;
|
|
}
|
|
// strip all my addresses from the list of recipients
|
|
QStringList recipients = KPIM::splitEmailAddrList( toStr );
|
|
toStr = stripMyAddressesFromAddressList( recipients ).join(", ");
|
|
|
|
break;
|
|
}
|
|
case KMail::ReplyAll : {
|
|
QStringList recipients;
|
|
QStringList ccRecipients;
|
|
|
|
// add addresses from the Reply-To header to the list of recipients
|
|
if( !replyToStr.isEmpty() ) {
|
|
recipients += KPIM::splitEmailAddrList( replyToStr );
|
|
// strip all possible mailing list addresses from the list of Reply-To
|
|
// addresses
|
|
for ( QStringList::const_iterator it = mailingListAddresses.begin();
|
|
it != mailingListAddresses.end();
|
|
++it ) {
|
|
recipients = stripAddressFromAddressList( *it, recipients );
|
|
}
|
|
}
|
|
|
|
if ( !mailingListAddresses.isEmpty() ) {
|
|
// this is a mailing list message
|
|
if ( recipients.isEmpty() && !from().isEmpty() ) {
|
|
// The sender didn't set a Reply-to address, so we add the From
|
|
// address to the list of CC recipients.
|
|
ccRecipients += from();
|
|
kdDebug(5006) << "Added " << from() << " to the list of CC recipients"
|
|
<< endl;
|
|
}
|
|
// if it is a mailing list, add the posting address
|
|
recipients.prepend( mailingListAddresses[0] );
|
|
}
|
|
else {
|
|
// this is a normal message
|
|
if ( recipients.isEmpty() && !from().isEmpty() ) {
|
|
// in case of replying to a normal message only then add the From
|
|
// address to the list of recipients if there was no Reply-to address
|
|
recipients += from();
|
|
kdDebug(5006) << "Added " << from() << " to the list of recipients"
|
|
<< endl;
|
|
}
|
|
}
|
|
|
|
// strip all my addresses from the list of recipients
|
|
toStr = stripMyAddressesFromAddressList( recipients ).join(", ");
|
|
|
|
// merge To header and CC header into a list of CC recipients
|
|
if( !cc().isEmpty() || !to().isEmpty() ) {
|
|
QStringList list;
|
|
if (!to().isEmpty())
|
|
list += KPIM::splitEmailAddrList(to());
|
|
if (!cc().isEmpty())
|
|
list += KPIM::splitEmailAddrList(cc());
|
|
for( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) {
|
|
if( !addressIsInAddressList( *it, recipients )
|
|
&& !addressIsInAddressList( *it, ccRecipients ) ) {
|
|
ccRecipients += *it;
|
|
kdDebug(5006) << "Added " << *it << " to the list of CC recipients"
|
|
<< endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !ccRecipients.isEmpty() ) {
|
|
// strip all my addresses from the list of CC recipients
|
|
ccRecipients = stripMyAddressesFromAddressList( ccRecipients );
|
|
|
|
// in case of a reply to self toStr might be empty. if that's the case
|
|
// then propagate a cc recipient to To: (if there is any).
|
|
if ( toStr.isEmpty() && !ccRecipients.isEmpty() ) {
|
|
toStr = ccRecipients[0];
|
|
ccRecipients.pop_front();
|
|
}
|
|
|
|
msg->setCc( ccRecipients.join(", ") );
|
|
}
|
|
|
|
if ( toStr.isEmpty() && !recipients.isEmpty() ) {
|
|
// reply to self without other recipients
|
|
toStr = recipients[0];
|
|
}
|
|
break;
|
|
}
|
|
case KMail::ReplyAuthor : {
|
|
if ( !replyToStr.isEmpty() ) {
|
|
QStringList recipients = KPIM::splitEmailAddrList( replyToStr );
|
|
// strip the mailing list post address from the list of Reply-To
|
|
// addresses since we want to reply in private
|
|
for ( QStringList::const_iterator it = mailingListAddresses.begin();
|
|
it != mailingListAddresses.end();
|
|
++it ) {
|
|
recipients = stripAddressFromAddressList( *it, recipients );
|
|
}
|
|
if ( !recipients.isEmpty() ) {
|
|
toStr = recipients.join(", ");
|
|
}
|
|
else {
|
|
// there was only the mailing list post address in the Reply-To header,
|
|
// so use the From address instead
|
|
toStr = from();
|
|
}
|
|
}
|
|
else if ( !from().isEmpty() ) {
|
|
toStr = from();
|
|
}
|
|
replyStr = sReplyStr; // reply to author, so use "On ... you wrote:"
|
|
replyAll = false;
|
|
break;
|
|
}
|
|
case KMail::ReplyNone : {
|
|
// the addressees will be set by the caller
|
|
}
|
|
}
|
|
|
|
msg->setTo(toStr);
|
|
|
|
refStr = getRefStr();
|
|
if (!refStr.isEmpty())
|
|
msg->setReferences(refStr);
|
|
//In-Reply-To = original msg-id
|
|
msg->setReplyToId(msgId());
|
|
|
|
// if (!noQuote) {
|
|
// if( selectionIsBody ){
|
|
// QCString cStr = selection.latin1();
|
|
// msg->setBody( cStr );
|
|
// }else{
|
|
// msg->setBody(asQuotedString(replyStr + "\n", sIndentPrefixStr, selection,
|
|
// sSmartQuote, allowDecryption).utf8());
|
|
// }
|
|
// }
|
|
|
|
msg->setSubject( replySubject() );
|
|
|
|
TemplateParser parser( msg, (replyAll ? TemplateParser::ReplyAll : TemplateParser::Reply),
|
|
selection, sSmartQuote, noQuote, allowDecryption, selectionIsBody );
|
|
if ( !tmpl.isEmpty() ) {
|
|
parser.process( tmpl, this );
|
|
} else {
|
|
parser.process( this );
|
|
}
|
|
|
|
// setStatus(KMMsgStatusReplied);
|
|
msg->link(this, KMMsgStatusReplied);
|
|
|
|
if ( parent() && parent()->putRepliesInSameFolder() )
|
|
msg->setFcc( parent()->idString() );
|
|
|
|
// replies to an encrypted message should be encrypted as well
|
|
if ( encryptionState() == KMMsgPartiallyEncrypted ||
|
|
encryptionState() == KMMsgFullyEncrypted ) {
|
|
msg->setEncryptionState( KMMsgFullyEncrypted );
|
|
}
|
|
|
|
return msg;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QCString KMMessage::getRefStr() const
|
|
{
|
|
QCString firstRef, lastRef, refStr, retRefStr;
|
|
int i, j;
|
|
|
|
refStr = headerField("References").stripWhiteSpace().latin1();
|
|
|
|
if (refStr.isEmpty())
|
|
return headerField("Message-Id").latin1();
|
|
|
|
i = refStr.find('<');
|
|
j = refStr.find('>');
|
|
firstRef = refStr.mid(i, j-i+1);
|
|
if (!firstRef.isEmpty())
|
|
retRefStr = firstRef + ' ';
|
|
|
|
i = refStr.findRev('<');
|
|
j = refStr.findRev('>');
|
|
|
|
lastRef = refStr.mid(i, j-i+1);
|
|
if (!lastRef.isEmpty() && lastRef != firstRef)
|
|
retRefStr += lastRef + ' ';
|
|
|
|
retRefStr += headerField("Message-Id").latin1();
|
|
return retRefStr;
|
|
}
|
|
|
|
|
|
KMMessage* KMMessage::createRedirect( const QString &toStr )
|
|
{
|
|
// copy the message 1:1
|
|
KMMessage* msg = new KMMessage( new DwMessage( *this->mMsg ) );
|
|
KMMessagePart msgPart;
|
|
|
|
uint id = 0;
|
|
QString strId = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace();
|
|
if ( !strId.isEmpty())
|
|
id = strId.toUInt();
|
|
const KPIM::Identity & ident =
|
|
kmkernel->identityManager()->identityForUoidOrDefault( id );
|
|
|
|
// X-KMail-Redirect-From: content
|
|
QString strByWayOf = QString("%1 (by way of %2 <%3>)")
|
|
.arg( from() )
|
|
.arg( ident.fullName() )
|
|
.arg( ident.emailAddr() );
|
|
|
|
// Resent-From: content
|
|
QString strFrom = QString("%1 <%2>")
|
|
.arg( ident.fullName() )
|
|
.arg( ident.emailAddr() );
|
|
|
|
// format the current date to be used in Resent-Date:
|
|
QString origDate = msg->headerField( "Date" );
|
|
msg->setDateToday();
|
|
QString newDate = msg->headerField( "Date" );
|
|
// make sure the Date: header is valid
|
|
if ( origDate.isEmpty() )
|
|
msg->removeHeaderField( "Date" );
|
|
else
|
|
msg->setHeaderField( "Date", origDate );
|
|
|
|
// prepend Resent-*: headers (c.f. RFC2822 3.6.6)
|
|
msg->setHeaderField( "Resent-Message-ID", generateMessageId( msg->sender() ),
|
|
Structured, true);
|
|
msg->setHeaderField( "Resent-Date", newDate, Structured, true );
|
|
msg->setHeaderField( "Resent-To", toStr, Address, true );
|
|
msg->setHeaderField( "Resent-From", strFrom, Address, true );
|
|
|
|
msg->setHeaderField( "X-KMail-Redirect-From", strByWayOf );
|
|
msg->setHeaderField( "X-KMail-Recipients", toStr, Address );
|
|
|
|
msg->link(this, KMMsgStatusForwarded);
|
|
|
|
return msg;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QCString KMMessage::createForwardBody()
|
|
{
|
|
QString s;
|
|
QCString str;
|
|
|
|
if (sHeaderStrategy == HeaderStrategy::all()) {
|
|
s = "\n\n---------- " + sForwardStr + " ----------\n\n";
|
|
s += headerAsString();
|
|
str = asQuotedString(s, "", QString::null, false, false).utf8();
|
|
str += "\n-------------------------------------------------------\n";
|
|
} else {
|
|
s = "\n\n---------- " + sForwardStr + " ----------\n\n";
|
|
s += "Subject: " + subject() + "\n";
|
|
s += "Date: "
|
|
+ KMime::DateFormatter::formatDate( KMime::DateFormatter::Localized,
|
|
date(), sReplyLanguage, false )
|
|
+ "\n";
|
|
s += "From: " + from() + "\n";
|
|
s += "To: " + to() + "\n";
|
|
if (!cc().isEmpty()) s += "Cc: " + cc() + "\n";
|
|
s += "\n";
|
|
str = asQuotedString(s, "", QString::null, false, false).utf8();
|
|
str += "\n-------------------------------------------------------\n";
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
void KMMessage::sanitizeHeaders( const QStringList& whiteList )
|
|
{
|
|
// Strip out all headers apart from the content description and other
|
|
// whitelisted ones, because we don't want to inherit them.
|
|
DwHeaders& header = mMsg->Headers();
|
|
DwField* field = header.FirstField();
|
|
DwField* nextField;
|
|
while (field)
|
|
{
|
|
nextField = field->Next();
|
|
if ( field->FieldNameStr().find( "ontent" ) == DwString::npos
|
|
&& !whiteList.contains( QString::fromLatin1( field->FieldNameStr().c_str() ) ) )
|
|
header.RemoveField(field);
|
|
field = nextField;
|
|
}
|
|
mMsg->Assemble();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
KMMessage* KMMessage::createForward( const QString &tmpl /* = QString::null */ )
|
|
{
|
|
KMMessage* msg = new KMMessage();
|
|
QString id;
|
|
|
|
// If this is a multipart mail or if the main part is only the text part,
|
|
// Make an identical copy of the mail, minus headers, so attachments are
|
|
// preserved
|
|
if ( type() == DwMime::kTypeMultipart ||
|
|
( type() == DwMime::kTypeText && subtype() == DwMime::kSubtypePlain ) ) {
|
|
// ## slow, we could probably use: delete msg->mMsg; msg->mMsg = new DwMessage( this->mMsg );
|
|
msg->fromDwString( this->asDwString() );
|
|
// remember the type and subtype, initFromMessage sets the contents type to
|
|
// text/plain, via initHeader, for unclear reasons
|
|
const int type = msg->type();
|
|
const int subtype = msg->subtype();
|
|
|
|
msg->sanitizeHeaders();
|
|
|
|
// strip blacklisted parts
|
|
QStringList blacklist = GlobalSettings::self()->mimetypesToStripWhenInlineForwarding();
|
|
for ( QStringList::Iterator it = blacklist.begin(); it != blacklist.end(); ++it ) {
|
|
QString entry = (*it);
|
|
int sep = entry.find( '/' );
|
|
QCString type = entry.left( sep ).latin1();
|
|
QCString subtype = entry.mid( sep+1 ).latin1();
|
|
kdDebug( 5006 ) << "Looking for blacklisted type: " << type << "/" << subtype << endl;
|
|
while ( DwBodyPart * part = msg->findDwBodyPart( type, subtype ) ) {
|
|
msg->mMsg->Body().RemoveBodyPart( part );
|
|
}
|
|
}
|
|
msg->mMsg->Assemble();
|
|
|
|
msg->initFromMessage( this );
|
|
//restore type
|
|
msg->setType( type );
|
|
msg->setSubtype( subtype );
|
|
} else if( type() == DwMime::kTypeText && subtype() == DwMime::kSubtypeHtml ) {
|
|
// This is non-multipart html mail. Let`s make it text/plain and allow
|
|
// template parser do the hard job.
|
|
msg->initFromMessage( this );
|
|
msg->setType( DwMime::kTypeText );
|
|
msg->setSubtype( DwMime::kSubtypeHtml );
|
|
msg->mNeedsAssembly = true;
|
|
msg->cleanupHeader();
|
|
} else {
|
|
// This is a non-multipart, non-text mail (e.g. text/calendar). Construct
|
|
// a multipart/mixed mail and add the original body as an attachment.
|
|
msg->initFromMessage( this );
|
|
msg->removeHeaderField("Content-Type");
|
|
msg->removeHeaderField("Content-Transfer-Encoding");
|
|
// Modify the ContentType directly (replaces setAutomaticFields(true))
|
|
DwHeaders & header = msg->mMsg->Headers();
|
|
header.MimeVersion().FromString("1.0");
|
|
DwMediaType & contentType = msg->dwContentType();
|
|
contentType.SetType( DwMime::kTypeMultipart );
|
|
contentType.SetSubtype( DwMime::kSubtypeMixed );
|
|
contentType.CreateBoundary(0);
|
|
contentType.Assemble();
|
|
|
|
// empty text part
|
|
KMMessagePart msgPart;
|
|
bodyPart( 0, &msgPart );
|
|
msg->addBodyPart(&msgPart);
|
|
// the old contents of the mail
|
|
KMMessagePart secondPart;
|
|
secondPart.setType( type() );
|
|
secondPart.setSubtype( subtype() );
|
|
secondPart.setBody( mMsg->Body().AsString() );
|
|
// use the headers of the original mail
|
|
applyHeadersToMessagePart( mMsg->Headers(), &secondPart );
|
|
msg->addBodyPart(&secondPart);
|
|
msg->mNeedsAssembly = true;
|
|
msg->cleanupHeader();
|
|
}
|
|
// QString st = QString::fromUtf8(createForwardBody());
|
|
|
|
msg->setSubject( forwardSubject() );
|
|
|
|
TemplateParser parser( msg, TemplateParser::Forward,
|
|
asPlainText( false, false ),
|
|
false, false, false, false);
|
|
if ( !tmpl.isEmpty() ) {
|
|
parser.process( tmpl, this );
|
|
} else {
|
|
parser.process( this );
|
|
}
|
|
|
|
// QCString encoding = autoDetectCharset(charset(), sPrefCharsets, msg->body());
|
|
// if (encoding.isEmpty()) encoding = "utf-8";
|
|
// msg->setCharset(encoding);
|
|
|
|
// force utf-8
|
|
// msg->setCharset( "utf-8" );
|
|
|
|
msg->link(this, KMMsgStatusForwarded);
|
|
return msg;
|
|
}
|
|
|
|
static const struct {
|
|
const char * dontAskAgainID;
|
|
bool canDeny;
|
|
const char * text;
|
|
} mdnMessageBoxes[] = {
|
|
{ "mdnNormalAsk", true,
|
|
I18N_NOOP("This message contains a request to return a notification "
|
|
"about your reception of the message.\n"
|
|
"You can either ignore the request or let KMail send a "
|
|
"\"denied\" or normal response.") },
|
|
{ "mdnUnknownOption", false,
|
|
I18N_NOOP("This message contains a request to send a notification "
|
|
"about your reception of the message.\n"
|
|
"It contains a processing instruction that is marked as "
|
|
"\"required\", but which is unknown to KMail.\n"
|
|
"You can either ignore the request or let KMail send a "
|
|
"\"failed\" response.") },
|
|
{ "mdnMultipleAddressesInReceiptTo", true,
|
|
I18N_NOOP("This message contains a request to send a notification "
|
|
"about your reception of the message,\n"
|
|
"but it is requested to send the notification to more "
|
|
"than one address.\n"
|
|
"You can either ignore the request or let KMail send a "
|
|
"\"denied\" or normal response.") },
|
|
{ "mdnReturnPathEmpty", true,
|
|
I18N_NOOP("This message contains a request to send a notification "
|
|
"about your reception of the message,\n"
|
|
"but there is no return-path set.\n"
|
|
"You can either ignore the request or let KMail send a "
|
|
"\"denied\" or normal response.") },
|
|
{ "mdnReturnPathNotInReceiptTo", true,
|
|
I18N_NOOP("This message contains a request to send a notification "
|
|
"about your reception of the message,\n"
|
|
"but the return-path address differs from the address "
|
|
"the notification was requested to be sent to.\n"
|
|
"You can either ignore the request or let KMail send a "
|
|
"\"denied\" or normal response.") },
|
|
};
|
|
|
|
static const int numMdnMessageBoxes
|
|
= sizeof mdnMessageBoxes / sizeof *mdnMessageBoxes;
|
|
|
|
|
|
static int requestAdviceOnMDN( const char * what ) {
|
|
for ( int i = 0 ; i < numMdnMessageBoxes ; ++i )
|
|
if ( !qstrcmp( what, mdnMessageBoxes[i].dontAskAgainID ) )
|
|
if ( mdnMessageBoxes[i].canDeny ) {
|
|
const KCursorSaver saver( QCursor::ArrowCursor );
|
|
int answer = QMessageBox::information( 0,
|
|
i18n("Message Disposition Notification Request"),
|
|
i18n( mdnMessageBoxes[i].text ),
|
|
i18n("&Ignore"), i18n("Send \"&denied\""), i18n("&Send") );
|
|
return answer ? answer + 1 : 0 ; // map to "mode" in createMDN
|
|
} else {
|
|
const KCursorSaver saver( QCursor::ArrowCursor );
|
|
int answer = QMessageBox::information( 0,
|
|
i18n("Message Disposition Notification Request"),
|
|
i18n( mdnMessageBoxes[i].text ),
|
|
i18n("&Ignore"), i18n("&Send") );
|
|
return answer ? answer + 2 : 0 ; // map to "mode" in createMDN
|
|
}
|
|
kdWarning(5006) << "didn't find data for message box \""
|
|
<< what << "\"" << endl;
|
|
return 0;
|
|
}
|
|
|
|
KMMessage* KMMessage::createMDN( MDN::ActionMode a,
|
|
MDN::DispositionType d,
|
|
bool allowGUI,
|
|
QValueList<MDN::DispositionModifier> m )
|
|
{
|
|
// RFC 2298: At most one MDN may be issued on behalf of each
|
|
// particular recipient by their user agent. That is, once an MDN
|
|
// has been issued on behalf of a recipient, no further MDNs may be
|
|
// issued on behalf of that recipient, even if another disposition
|
|
// is performed on the message.
|
|
//#define MDN_DEBUG 1
|
|
#ifndef MDN_DEBUG
|
|
if ( mdnSentState() != KMMsgMDNStateUnknown &&
|
|
mdnSentState() != KMMsgMDNNone )
|
|
return 0;
|
|
#else
|
|
char st[2]; st[0] = (char)mdnSentState(); st[1] = 0;
|
|
kdDebug(5006) << "mdnSentState() == '" << st << "'" << endl;
|
|
#endif
|
|
|
|
// RFC 2298: An MDN MUST NOT be generated in response to an MDN.
|
|
if ( findDwBodyPart( DwMime::kTypeMessage,
|
|
DwMime::kSubtypeDispositionNotification ) ) {
|
|
setMDNSentState( KMMsgMDNIgnore );
|
|
return 0;
|
|
}
|
|
|
|
// extract where to send to:
|
|
QString receiptTo = headerField("Disposition-Notification-To");
|
|
if ( receiptTo.stripWhiteSpace().isEmpty() ) return 0;
|
|
receiptTo.remove( '\n' );
|
|
|
|
|
|
MDN::SendingMode s = MDN::SentAutomatically; // set to manual if asked user
|
|
QString special; // fill in case of error, warning or failure
|
|
KConfigGroup mdnConfig( KMKernel::config(), "MDN" );
|
|
|
|
// default:
|
|
int mode = mdnConfig.readNumEntry( "default-policy", 0 );
|
|
if ( !mode || mode < 0 || mode > 3 ) {
|
|
// early out for ignore:
|
|
setMDNSentState( KMMsgMDNIgnore );
|
|
return 0;
|
|
}
|
|
|
|
// RFC 2298: An importance of "required" indicates that
|
|
// interpretation of the parameter is necessary for proper
|
|
// generation of an MDN in response to this request. If a UA does
|
|
// not understand the meaning of the parameter, it MUST NOT generate
|
|
// an MDN with any disposition type other than "failed" in response
|
|
// to the request.
|
|
QString notificationOptions = headerField("Disposition-Notification-Options");
|
|
if ( notificationOptions.contains( "required", false ) ) {
|
|
// ### hacky; should parse...
|
|
// There is a required option that we don't understand. We need to
|
|
// ask the user what we should do:
|
|
if ( !allowGUI ) return 0; // don't setMDNSentState here!
|
|
mode = requestAdviceOnMDN( "mdnUnknownOption" );
|
|
s = MDN::SentManually;
|
|
|
|
special = i18n("Header \"Disposition-Notification-Options\" contained "
|
|
"required, but unknown parameter");
|
|
d = MDN::Failed;
|
|
m.clear(); // clear modifiers
|
|
}
|
|
|
|
// RFC 2298: [ Confirmation from the user SHOULD be obtained (or no
|
|
// MDN sent) ] if there is more than one distinct address in the
|
|
// Disposition-Notification-To header.
|
|
kdDebug(5006) << "KPIM::splitEmailAddrList(receiptTo): "
|
|
<< KPIM::splitEmailAddrList(receiptTo).join("\n") << endl;
|
|
if ( KPIM::splitEmailAddrList(receiptTo).count() > 1 ) {
|
|
if ( !allowGUI ) return 0; // don't setMDNSentState here!
|
|
mode = requestAdviceOnMDN( "mdnMultipleAddressesInReceiptTo" );
|
|
s = MDN::SentManually;
|
|
}
|
|
|
|
// RFC 2298: MDNs SHOULD NOT be sent automatically if the address in
|
|
// the Disposition-Notification-To header differs from the address
|
|
// in the Return-Path header. [...] Confirmation from the user
|
|
// SHOULD be obtained (or no MDN sent) if there is no Return-Path
|
|
// header in the message [...]
|
|
AddrSpecList returnPathList = extractAddrSpecs("Return-Path");
|
|
QString returnPath = returnPathList.isEmpty() ? QString::null
|
|
: returnPathList.front().localPart + '@' + returnPathList.front().domain ;
|
|
kdDebug(5006) << "clean return path: " << returnPath << endl;
|
|
if ( returnPath.isEmpty() || !receiptTo.contains( returnPath, false ) ) {
|
|
if ( !allowGUI ) return 0; // don't setMDNSentState here!
|
|
mode = requestAdviceOnMDN( returnPath.isEmpty() ?
|
|
"mdnReturnPathEmpty" :
|
|
"mdnReturnPathNotInReceiptTo" );
|
|
s = MDN::SentManually;
|
|
}
|
|
|
|
if ( a != KMime::MDN::AutomaticAction ) {
|
|
//TODO: only ingore user settings for AutomaticAction if requested
|
|
if ( mode == 1 ) { // ask
|
|
if ( !allowGUI ) return 0; // don't setMDNSentState here!
|
|
mode = requestAdviceOnMDN( "mdnNormalAsk" );
|
|
s = MDN::SentManually; // asked user
|
|
}
|
|
|
|
switch ( mode ) {
|
|
case 0: // ignore:
|
|
setMDNSentState( KMMsgMDNIgnore );
|
|
return 0;
|
|
default:
|
|
case 1:
|
|
kdFatal(5006) << "KMMessage::createMDN(): The \"ask\" mode should "
|
|
<< "never appear here!" << endl;
|
|
break;
|
|
case 2: // deny
|
|
d = MDN::Denied;
|
|
m.clear();
|
|
break;
|
|
case 3:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// extract where to send from:
|
|
QString finalRecipient = kmkernel->identityManager()
|
|
->identityForUoidOrDefault( identityUoid() ).fullEmailAddr();
|
|
|
|
//
|
|
// Generate message:
|
|
//
|
|
|
|
KMMessage * receipt = new KMMessage();
|
|
receipt->initFromMessage( this );
|
|
receipt->removeHeaderField("Content-Type");
|
|
receipt->removeHeaderField("Content-Transfer-Encoding");
|
|
// Modify the ContentType directly (replaces setAutomaticFields(true))
|
|
DwHeaders & header = receipt->mMsg->Headers();
|
|
header.MimeVersion().FromString("1.0");
|
|
DwMediaType & contentType = receipt->dwContentType();
|
|
contentType.SetType( DwMime::kTypeMultipart );
|
|
contentType.SetSubtype( DwMime::kSubtypeReport );
|
|
contentType.CreateBoundary(0);
|
|
receipt->mNeedsAssembly = true;
|
|
receipt->setContentTypeParam( "report-type", "disposition-notification" );
|
|
|
|
QString description = replaceHeadersInString( MDN::descriptionFor( d, m ) );
|
|
|
|
// text/plain part:
|
|
KMMessagePart firstMsgPart;
|
|
firstMsgPart.setTypeStr( "text" );
|
|
firstMsgPart.setSubtypeStr( "plain" );
|
|
firstMsgPart.setBodyFromUnicode( description );
|
|
receipt->addBodyPart( &firstMsgPart );
|
|
|
|
// message/disposition-notification part:
|
|
KMMessagePart secondMsgPart;
|
|
secondMsgPart.setType( DwMime::kTypeMessage );
|
|
secondMsgPart.setSubtype( DwMime::kSubtypeDispositionNotification );
|
|
//secondMsgPart.setCharset( "us-ascii" );
|
|
//secondMsgPart.setCteStr( "7bit" );
|
|
secondMsgPart.setBodyEncoded( MDN::dispositionNotificationBodyContent(
|
|
finalRecipient,
|
|
rawHeaderField("Original-Recipient"),
|
|
id(), /* Message-ID */
|
|
d, a, s, m, special ) );
|
|
receipt->addBodyPart( &secondMsgPart );
|
|
|
|
// message/rfc822 or text/rfc822-headers body part:
|
|
int num = mdnConfig.readNumEntry( "quote-message", 0 );
|
|
if ( num < 0 || num > 2 ) num = 0;
|
|
MDN::ReturnContent returnContent = static_cast<MDN::ReturnContent>( num );
|
|
|
|
KMMessagePart thirdMsgPart;
|
|
switch ( returnContent ) {
|
|
case MDN::All:
|
|
thirdMsgPart.setTypeStr( "message" );
|
|
thirdMsgPart.setSubtypeStr( "rfc822" );
|
|
thirdMsgPart.setBody( asSendableString() );
|
|
receipt->addBodyPart( &thirdMsgPart );
|
|
break;
|
|
case MDN::HeadersOnly:
|
|
thirdMsgPart.setTypeStr( "text" );
|
|
thirdMsgPart.setSubtypeStr( "rfc822-headers" );
|
|
thirdMsgPart.setBody( headerAsSendableString() );
|
|
receipt->addBodyPart( &thirdMsgPart );
|
|
break;
|
|
case MDN::Nothing:
|
|
default:
|
|
break;
|
|
};
|
|
|
|
receipt->setTo( receiptTo );
|
|
receipt->setSubject( "Message Disposition Notification" );
|
|
receipt->setReplyToId( msgId() );
|
|
receipt->setReferences( getRefStr() );
|
|
|
|
receipt->cleanupHeader();
|
|
|
|
kdDebug(5006) << "final message:\n" + receipt->asString() << endl;
|
|
|
|
//
|
|
// Set "MDN sent" status:
|
|
//
|
|
KMMsgMDNSentState state = KMMsgMDNStateUnknown;
|
|
switch ( d ) {
|
|
case MDN::Displayed: state = KMMsgMDNDisplayed; break;
|
|
case MDN::Deleted: state = KMMsgMDNDeleted; break;
|
|
case MDN::Dispatched: state = KMMsgMDNDispatched; break;
|
|
case MDN::Processed: state = KMMsgMDNProcessed; break;
|
|
case MDN::Denied: state = KMMsgMDNDenied; break;
|
|
case MDN::Failed: state = KMMsgMDNFailed; break;
|
|
};
|
|
setMDNSentState( state );
|
|
|
|
return receipt;
|
|
}
|
|
|
|
QString KMMessage::replaceHeadersInString( const QString & s ) const {
|
|
QString result = s;
|
|
QRegExp rx( "\\$\\{([a-z0-9-]+)\\}", false );
|
|
Q_ASSERT( rx.isValid() );
|
|
|
|
QRegExp rxDate( "\\$\\{date\\}" );
|
|
Q_ASSERT( rxDate.isValid() );
|
|
|
|
QString sDate = KMime::DateFormatter::formatDate(
|
|
KMime::DateFormatter::Localized, date() );
|
|
|
|
int idx = 0;
|
|
if( ( idx = rxDate.search( result, idx ) ) != -1 ) {
|
|
result.replace( idx, rxDate.matchedLength(), sDate );
|
|
}
|
|
|
|
idx = 0;
|
|
while ( ( idx = rx.search( result, idx ) ) != -1 ) {
|
|
QString replacement = headerField( rx.cap(1).latin1() );
|
|
result.replace( idx, rx.matchedLength(), replacement );
|
|
idx += replacement.length();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
KMMessage* KMMessage::createDeliveryReceipt() const
|
|
{
|
|
QString str, receiptTo;
|
|
KMMessage *receipt;
|
|
|
|
receiptTo = headerField("Disposition-Notification-To");
|
|
if ( receiptTo.stripWhiteSpace().isEmpty() ) return 0;
|
|
receiptTo.remove( '\n' );
|
|
|
|
receipt = new KMMessage;
|
|
receipt->initFromMessage(this);
|
|
receipt->setTo(receiptTo);
|
|
receipt->setSubject(i18n("Receipt: ") + subject());
|
|
|
|
str = "Your message was successfully delivered.";
|
|
str += "\n\n---------- Message header follows ----------\n";
|
|
str += headerAsString();
|
|
str += "--------------------------------------------\n";
|
|
// Conversion to latin1 is correct here as Mail headers should contain
|
|
// ascii only
|
|
receipt->setBody(str.latin1());
|
|
receipt->setAutomaticFields();
|
|
|
|
return receipt;
|
|
}
|
|
|
|
|
|
void KMMessage::applyIdentity( uint id )
|
|
{
|
|
const KPIM::Identity & ident =
|
|
kmkernel->identityManager()->identityForUoidOrDefault( id );
|
|
|
|
if(ident.fullEmailAddr().isEmpty())
|
|
setFrom("");
|
|
else
|
|
setFrom(ident.fullEmailAddr());
|
|
|
|
if(ident.replyToAddr().isEmpty())
|
|
setReplyTo("");
|
|
else
|
|
setReplyTo(ident.replyToAddr());
|
|
|
|
if(ident.bcc().isEmpty())
|
|
setBcc("");
|
|
else
|
|
setBcc(ident.bcc());
|
|
|
|
if (ident.organization().isEmpty())
|
|
removeHeaderField("Organization");
|
|
else
|
|
setHeaderField("Organization", ident.organization());
|
|
|
|
if (ident.isDefault())
|
|
removeHeaderField("X-KMail-Identity");
|
|
else
|
|
setHeaderField("X-KMail-Identity", QString::number( ident.uoid() ));
|
|
|
|
if ( ident.transport().isEmpty() )
|
|
removeHeaderField( "X-KMail-Transport" );
|
|
else
|
|
setHeaderField( "X-KMail-Transport", ident.transport() );
|
|
|
|
if ( ident.fcc().isEmpty() )
|
|
setFcc( QString::null );
|
|
else
|
|
setFcc( ident.fcc() );
|
|
|
|
if ( ident.drafts().isEmpty() )
|
|
setDrafts( QString::null );
|
|
else
|
|
setDrafts( ident.drafts() );
|
|
|
|
if ( ident.templates().isEmpty() )
|
|
setTemplates( QString::null );
|
|
else
|
|
setTemplates( ident.templates() );
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::initHeader( uint id )
|
|
{
|
|
applyIdentity( id );
|
|
setTo("");
|
|
setSubject("");
|
|
setDateToday();
|
|
|
|
setHeaderField("User-Agent", "KMail/" KMAIL_VERSION );
|
|
// This will allow to change Content-Type:
|
|
setHeaderField("Content-Type","text/plain");
|
|
}
|
|
|
|
uint KMMessage::identityUoid() const {
|
|
QString idString = headerField("X-KMail-Identity").stripWhiteSpace();
|
|
bool ok = false;
|
|
int id = idString.toUInt( &ok );
|
|
|
|
if ( !ok || id == 0 )
|
|
id = kmkernel->identityManager()->identityForAddress( to() + ", " + cc() ).uoid();
|
|
if ( id == 0 && parent() )
|
|
id = parent()->identity();
|
|
|
|
return id;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::initFromMessage(const KMMessage *msg, bool idHeaders)
|
|
{
|
|
uint id = msg->identityUoid();
|
|
|
|
if ( idHeaders ) initHeader(id);
|
|
else setHeaderField("X-KMail-Identity", QString::number(id));
|
|
if (!msg->headerField("X-KMail-Transport").isEmpty())
|
|
setHeaderField("X-KMail-Transport", msg->headerField("X-KMail-Transport"));
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::cleanupHeader()
|
|
{
|
|
DwHeaders& header = mMsg->Headers();
|
|
DwField* field = header.FirstField();
|
|
DwField* nextField;
|
|
|
|
if (mNeedsAssembly) mMsg->Assemble();
|
|
mNeedsAssembly = false;
|
|
|
|
while (field)
|
|
{
|
|
nextField = field->Next();
|
|
if (field->FieldBody()->AsString().empty())
|
|
{
|
|
header.RemoveField(field);
|
|
mNeedsAssembly = true;
|
|
}
|
|
field = nextField;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setAutomaticFields(bool aIsMulti)
|
|
{
|
|
DwHeaders& header = mMsg->Headers();
|
|
header.MimeVersion().FromString("1.0");
|
|
|
|
if (aIsMulti || numBodyParts() > 1)
|
|
{
|
|
// Set the type to 'Multipart' and the subtype to 'Mixed'
|
|
DwMediaType& contentType = dwContentType();
|
|
contentType.SetType( DwMime::kTypeMultipart);
|
|
contentType.SetSubtype(DwMime::kSubtypeMixed );
|
|
|
|
// Create a random printable string and set it as the boundary parameter
|
|
contentType.CreateBoundary(0);
|
|
}
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::dateStr() const
|
|
{
|
|
KConfigGroup general( KMKernel::config(), "General" );
|
|
DwHeaders& header = mMsg->Headers();
|
|
time_t unixTime;
|
|
|
|
if (!header.HasDate()) return "";
|
|
unixTime = header.Date().AsUnixTime();
|
|
|
|
//kdDebug(5006)<<"#### Date = "<<header.Date().AsString().c_str()<<endl;
|
|
|
|
return KMime::DateFormatter::formatDate(
|
|
static_cast<KMime::DateFormatter::FormatType>(general.readNumEntry( "dateFormat", KMime::DateFormatter::Fancy )),
|
|
unixTime, general.readEntry( "customDateFormat" ));
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QCString KMMessage::dateShortStr() const
|
|
{
|
|
DwHeaders& header = mMsg->Headers();
|
|
time_t unixTime;
|
|
|
|
if (!header.HasDate()) return "";
|
|
unixTime = header.Date().AsUnixTime();
|
|
|
|
QCString result = ctime(&unixTime);
|
|
int len = result.length();
|
|
if (result[len-1]=='\n')
|
|
result.truncate(len-1);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::dateIsoStr() const
|
|
{
|
|
DwHeaders& header = mMsg->Headers();
|
|
time_t unixTime;
|
|
|
|
if (!header.HasDate()) return "";
|
|
unixTime = header.Date().AsUnixTime();
|
|
|
|
char cstr[64];
|
|
strftime(cstr, 63, "%Y-%m-%d %H:%M:%S", localtime(&unixTime));
|
|
return QString(cstr);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
time_t KMMessage::date() const
|
|
{
|
|
time_t res = ( time_t )-1;
|
|
DwHeaders& header = mMsg->Headers();
|
|
if (header.HasDate())
|
|
res = header.Date().AsUnixTime();
|
|
return res;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setDateToday()
|
|
{
|
|
struct timeval tval;
|
|
gettimeofday(&tval, 0);
|
|
setDate((time_t)tval.tv_sec);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setDate(time_t aDate)
|
|
{
|
|
mDate = aDate;
|
|
mMsg->Headers().Date().FromCalendarTime(aDate);
|
|
mMsg->Headers().Date().Assemble();
|
|
mNeedsAssembly = true;
|
|
mDirty = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setDate(const QCString& aStr)
|
|
{
|
|
DwHeaders& header = mMsg->Headers();
|
|
|
|
header.Date().FromString(aStr);
|
|
header.Date().Parse();
|
|
mNeedsAssembly = true;
|
|
mDirty = true;
|
|
|
|
if (header.HasDate())
|
|
mDate = header.Date().AsUnixTime();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::to() const
|
|
{
|
|
// handle To same as Cc below, bug 80747
|
|
QValueList<QCString> rawHeaders = rawHeaderFields( "To" );
|
|
QStringList headers;
|
|
for ( QValueList<QCString>::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) {
|
|
headers << *it;
|
|
}
|
|
return KPIM::normalizeAddressesAndDecodeIDNs( headers.join( ", " ) );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setTo(const QString& aStr)
|
|
{
|
|
setHeaderField( "To", aStr, Address );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::toStrip() const
|
|
{
|
|
return stripEmailAddr( to() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::replyTo() const
|
|
{
|
|
return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("Reply-To") );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setReplyTo(const QString& aStr)
|
|
{
|
|
setHeaderField( "Reply-To", aStr, Address );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setReplyTo(KMMessage* aMsg)
|
|
{
|
|
setHeaderField( "Reply-To", aMsg->from(), Address );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::cc() const
|
|
{
|
|
// get the combined contents of all Cc headers (as workaround for invalid
|
|
// messages with multiple Cc headers)
|
|
QValueList<QCString> rawHeaders = rawHeaderFields( "Cc" );
|
|
QStringList headers;
|
|
for ( QValueList<QCString>::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) {
|
|
headers << *it;
|
|
}
|
|
return KPIM::normalizeAddressesAndDecodeIDNs( headers.join( ", " ) );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setCc(const QString& aStr)
|
|
{
|
|
setHeaderField( "Cc", aStr, Address );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::ccStrip() const
|
|
{
|
|
return stripEmailAddr( cc() );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::bcc() const
|
|
{
|
|
return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("Bcc") );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setBcc(const QString& aStr)
|
|
{
|
|
setHeaderField( "Bcc", aStr, Address );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::fcc() const
|
|
{
|
|
return headerField( "X-KMail-Fcc" );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setFcc( const QString &aStr )
|
|
{
|
|
setHeaderField( "X-KMail-Fcc", aStr );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setDrafts( const QString &aStr )
|
|
{
|
|
mDrafts = aStr;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setTemplates( const QString &aStr )
|
|
{
|
|
mTemplates = aStr;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::who() const
|
|
{
|
|
if (mParent)
|
|
return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField(mParent->whoField().utf8()) );
|
|
return from();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::from() const
|
|
{
|
|
return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("From") );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setFrom(const QString& bStr)
|
|
{
|
|
QString aStr = bStr;
|
|
if (aStr.isNull())
|
|
aStr = "";
|
|
setHeaderField( "From", aStr, Address );
|
|
mDirty = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::fromStrip() const
|
|
{
|
|
return stripEmailAddr( from() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::sender() const {
|
|
AddrSpecList asl = extractAddrSpecs( "Sender" );
|
|
if ( asl.empty() )
|
|
asl = extractAddrSpecs( "From" );
|
|
if ( asl.empty() )
|
|
return QString::null;
|
|
return asl.front().asString();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::subject() const
|
|
{
|
|
return headerField("Subject");
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setSubject(const QString& aStr)
|
|
{
|
|
setHeaderField("Subject",aStr);
|
|
mDirty = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::xmark() const
|
|
{
|
|
return headerField("X-KMail-Mark");
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setXMark(const QString& aStr)
|
|
{
|
|
setHeaderField("X-KMail-Mark", aStr);
|
|
mDirty = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::replyToId() const
|
|
{
|
|
int leftAngle, rightAngle;
|
|
QString replyTo, references;
|
|
|
|
replyTo = headerField("In-Reply-To");
|
|
// search the end of the (first) message id in the In-Reply-To header
|
|
rightAngle = replyTo.find( '>' );
|
|
if (rightAngle != -1)
|
|
replyTo.truncate( rightAngle + 1 );
|
|
// now search the start of the message id
|
|
leftAngle = replyTo.findRev( '<' );
|
|
if (leftAngle != -1)
|
|
replyTo = replyTo.mid( leftAngle );
|
|
|
|
// if we have found a good message id we can return immediately
|
|
// We ignore mangled In-Reply-To headers which are created by a
|
|
// misconfigured Mutt. They look like this <"from foo"@bar.baz>, i.e.
|
|
// they contain double quotes and spaces. We only check for '"'.
|
|
if (!replyTo.isEmpty() && (replyTo[0] == '<') &&
|
|
( -1 == replyTo.find( '"' ) ) )
|
|
return replyTo;
|
|
|
|
references = headerField("References");
|
|
leftAngle = references.findRev( '<' );
|
|
if (leftAngle != -1)
|
|
references = references.mid( leftAngle );
|
|
rightAngle = references.find( '>' );
|
|
if (rightAngle != -1)
|
|
references.truncate( rightAngle + 1 );
|
|
|
|
// if we found a good message id in the References header return it
|
|
if (!references.isEmpty() && references[0] == '<')
|
|
return references;
|
|
// else return the broken message id we found in the In-Reply-To header
|
|
else
|
|
return replyTo;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::replyToIdMD5() const {
|
|
return base64EncodedMD5( replyToId() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::references() const
|
|
{
|
|
int leftAngle, rightAngle;
|
|
QString references = headerField( "References" );
|
|
|
|
// keep the last two entries for threading
|
|
leftAngle = references.findRev( '<' );
|
|
leftAngle = references.findRev( '<', leftAngle - 1 );
|
|
if( leftAngle != -1 )
|
|
references = references.mid( leftAngle );
|
|
rightAngle = references.findRev( '>' );
|
|
if( rightAngle != -1 )
|
|
references.truncate( rightAngle + 1 );
|
|
|
|
if( !references.isEmpty() && references[0] == '<' )
|
|
return references;
|
|
else
|
|
return QString::null;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::replyToAuxIdMD5() const
|
|
{
|
|
QString result = references();
|
|
// references contains two items, use the first one
|
|
// (the second to last reference)
|
|
const int rightAngle = result.find( '>' );
|
|
if( rightAngle != -1 )
|
|
result.truncate( rightAngle + 1 );
|
|
|
|
return base64EncodedMD5( result );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::strippedSubjectMD5() const {
|
|
return base64EncodedMD5( stripOffPrefixes( subject() ), true /*utf8*/ );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::subjectMD5() const {
|
|
return base64EncodedMD5( subject(), true /*utf8*/ );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMessage::subjectIsPrefixed() const {
|
|
return subjectMD5() != strippedSubjectMD5();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setReplyToId(const QString& aStr)
|
|
{
|
|
setHeaderField("In-Reply-To", aStr);
|
|
mDirty = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::msgId() const
|
|
{
|
|
QString msgId = headerField("Message-Id");
|
|
|
|
// search the end of the message id
|
|
const int rightAngle = msgId.find( '>' );
|
|
if (rightAngle != -1)
|
|
msgId.truncate( rightAngle + 1 );
|
|
// now search the start of the message id
|
|
const int leftAngle = msgId.findRev( '<' );
|
|
if (leftAngle != -1)
|
|
msgId = msgId.mid( leftAngle );
|
|
return msgId;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::msgIdMD5() const {
|
|
return base64EncodedMD5( msgId() );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setMsgId(const QString& aStr)
|
|
{
|
|
setHeaderField("Message-Id", aStr);
|
|
mDirty = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
size_t KMMessage::msgSizeServer() const {
|
|
return headerField( "X-Length" ).toULong();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setMsgSizeServer(size_t size)
|
|
{
|
|
setHeaderField("X-Length", QCString().setNum(size));
|
|
mDirty = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
ulong KMMessage::UID() const {
|
|
return headerField( "X-UID" ).toULong();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setUID(ulong uid)
|
|
{
|
|
setHeaderField("X-UID", QCString().setNum(uid));
|
|
mDirty = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
AddressList KMMessage::splitAddrField( const QCString & str )
|
|
{
|
|
AddressList result;
|
|
const char * scursor = str.begin();
|
|
if ( !scursor )
|
|
return AddressList();
|
|
const char * const send = str.begin() + str.length();
|
|
if ( !parseAddressList( scursor, send, result ) )
|
|
kdDebug(5006) << "Error in address splitting: parseAddressList returned false!"
|
|
<< endl;
|
|
return result;
|
|
}
|
|
|
|
AddressList KMMessage::headerAddrField( const QCString & aName ) const {
|
|
return KMMessage::splitAddrField( rawHeaderField( aName ) );
|
|
}
|
|
|
|
AddrSpecList KMMessage::extractAddrSpecs( const QCString & header ) const {
|
|
AddressList al = headerAddrField( header );
|
|
AddrSpecList result;
|
|
for ( AddressList::const_iterator ait = al.begin() ; ait != al.end() ; ++ait )
|
|
for ( MailboxList::const_iterator mit = (*ait).mailboxList.begin() ; mit != (*ait).mailboxList.end() ; ++mit )
|
|
result.push_back( (*mit).addrSpec );
|
|
return result;
|
|
}
|
|
|
|
QCString KMMessage::rawHeaderField( const QCString & name ) const {
|
|
if ( name.isEmpty() ) return QCString();
|
|
|
|
DwHeaders & header = mMsg->Headers();
|
|
DwField * field = header.FindField( name );
|
|
|
|
if ( !field ) return QCString();
|
|
|
|
return header.FieldBody( name.data() ).AsString().c_str();
|
|
}
|
|
|
|
QValueList<QCString> KMMessage::rawHeaderFields( const QCString& field ) const
|
|
{
|
|
if ( field.isEmpty() || !mMsg->Headers().FindField( field ) )
|
|
return QValueList<QCString>();
|
|
|
|
std::vector<DwFieldBody*> v = mMsg->Headers().AllFieldBodies( field.data() );
|
|
QValueList<QCString> headerFields;
|
|
for ( uint i = 0; i < v.size(); ++i ) {
|
|
headerFields.append( v[i]->AsString().c_str() );
|
|
}
|
|
|
|
return headerFields;
|
|
}
|
|
|
|
QString KMMessage::headerField(const QCString& aName) const
|
|
{
|
|
if ( aName.isEmpty() )
|
|
return QString::null;
|
|
|
|
if ( !mMsg->Headers().FindField( aName ) )
|
|
return QString::null;
|
|
|
|
return decodeRFC2047String( mMsg->Headers().FieldBody( aName.data() ).AsString().c_str(),
|
|
charset() );
|
|
|
|
}
|
|
|
|
QStringList KMMessage::headerFields( const QCString& field ) const
|
|
{
|
|
if ( field.isEmpty() || !mMsg->Headers().FindField( field ) )
|
|
return QStringList();
|
|
|
|
std::vector<DwFieldBody*> v = mMsg->Headers().AllFieldBodies( field.data() );
|
|
QStringList headerFields;
|
|
for ( uint i = 0; i < v.size(); ++i ) {
|
|
headerFields.append( decodeRFC2047String( v[i]->AsString().c_str(), charset() ) );
|
|
}
|
|
|
|
return headerFields;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::removeHeaderField(const QCString& aName)
|
|
{
|
|
DwHeaders & header = mMsg->Headers();
|
|
DwField * field = header.FindField(aName);
|
|
if (!field) return;
|
|
|
|
header.RemoveField(field);
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::removeHeaderFields(const QCString& aName)
|
|
{
|
|
DwHeaders & header = mMsg->Headers();
|
|
while ( DwField * field = header.FindField(aName) ) {
|
|
header.RemoveField(field);
|
|
mNeedsAssembly = true;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setHeaderField( const QCString& aName, const QString& bValue,
|
|
HeaderFieldType type, bool prepend )
|
|
{
|
|
#if 0
|
|
if ( type != Unstructured )
|
|
kdDebug(5006) << "KMMessage::setHeaderField( \"" << aName << "\", \""
|
|
<< bValue << "\", " << type << " )" << endl;
|
|
#endif
|
|
if (aName.isEmpty()) return;
|
|
|
|
DwHeaders& header = mMsg->Headers();
|
|
|
|
DwString str;
|
|
DwField* field;
|
|
QCString aValue;
|
|
if (!bValue.isEmpty())
|
|
{
|
|
QString value = bValue;
|
|
if ( type == Address )
|
|
value = KPIM::normalizeAddressesAndEncodeIDNs( value );
|
|
#if 0
|
|
if ( type != Unstructured )
|
|
kdDebug(5006) << "value: \"" << value << "\"" << endl;
|
|
#endif
|
|
QCString encoding = autoDetectCharset( charset(), sPrefCharsets, value );
|
|
if (encoding.isEmpty())
|
|
encoding = "utf-8";
|
|
aValue = encodeRFC2047String( value, encoding );
|
|
#if 0
|
|
if ( type != Unstructured )
|
|
kdDebug(5006) << "aValue: \"" << aValue << "\"" << endl;
|
|
#endif
|
|
}
|
|
str = aName;
|
|
if (str[str.length()-1] != ':') str += ": ";
|
|
else str += ' ';
|
|
if ( !aValue.isEmpty() )
|
|
str += aValue;
|
|
if (str[str.length()-1] != '\n') str += '\n';
|
|
|
|
field = new DwField(str, mMsg);
|
|
field->Parse();
|
|
|
|
if ( prepend )
|
|
header.AddFieldAt( 1, field );
|
|
else
|
|
header.AddOrReplaceField( field );
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QCString KMMessage::typeStr() const
|
|
{
|
|
DwHeaders& header = mMsg->Headers();
|
|
if (header.HasContentType()) return header.ContentType().TypeStr().c_str();
|
|
else return "";
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int KMMessage::type() const
|
|
{
|
|
DwHeaders& header = mMsg->Headers();
|
|
if (header.HasContentType()) return header.ContentType().Type();
|
|
else return DwMime::kTypeNull;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setTypeStr(const QCString& aStr)
|
|
{
|
|
dwContentType().SetTypeStr(DwString(aStr));
|
|
dwContentType().Parse();
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setType(int aType)
|
|
{
|
|
dwContentType().SetType(aType);
|
|
dwContentType().Assemble();
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QCString KMMessage::subtypeStr() const
|
|
{
|
|
DwHeaders& header = mMsg->Headers();
|
|
if (header.HasContentType()) return header.ContentType().SubtypeStr().c_str();
|
|
else return "";
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int KMMessage::subtype() const
|
|
{
|
|
DwHeaders& header = mMsg->Headers();
|
|
if (header.HasContentType()) return header.ContentType().Subtype();
|
|
else return DwMime::kSubtypeNull;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setSubtypeStr(const QCString& aStr)
|
|
{
|
|
dwContentType().SetSubtypeStr(DwString(aStr));
|
|
dwContentType().Parse();
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setSubtype(int aSubtype)
|
|
{
|
|
dwContentType().SetSubtype(aSubtype);
|
|
dwContentType().Assemble();
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setDwMediaTypeParam( DwMediaType &mType,
|
|
const QCString& attr,
|
|
const QCString& val )
|
|
{
|
|
mType.Parse();
|
|
DwParameter *param = mType.FirstParameter();
|
|
while(param) {
|
|
if (!kasciistricmp(param->Attribute().c_str(), attr))
|
|
break;
|
|
else
|
|
param = param->Next();
|
|
}
|
|
if (!param){
|
|
param = new DwParameter;
|
|
param->SetAttribute(DwString( attr ));
|
|
mType.AddParameter( param );
|
|
}
|
|
else
|
|
mType.SetModified();
|
|
param->SetValue(DwString( val ));
|
|
mType.Assemble();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setContentTypeParam(const QCString& attr, const QCString& val)
|
|
{
|
|
if (mNeedsAssembly) mMsg->Assemble();
|
|
mNeedsAssembly = false;
|
|
setDwMediaTypeParam( dwContentType(), attr, val );
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QCString KMMessage::contentTransferEncodingStr() const
|
|
{
|
|
DwHeaders& header = mMsg->Headers();
|
|
if (header.HasContentTransferEncoding())
|
|
return header.ContentTransferEncoding().AsString().c_str();
|
|
else return "";
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int KMMessage::contentTransferEncoding() const
|
|
{
|
|
DwHeaders& header = mMsg->Headers();
|
|
if (header.HasContentTransferEncoding())
|
|
return header.ContentTransferEncoding().AsEnum();
|
|
else return DwMime::kCteNull;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setContentTransferEncodingStr(const QCString& aStr)
|
|
{
|
|
mMsg->Headers().ContentTransferEncoding().FromString(aStr);
|
|
mMsg->Headers().ContentTransferEncoding().Parse();
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setContentTransferEncoding(int aCte)
|
|
{
|
|
mMsg->Headers().ContentTransferEncoding().FromEnum(aCte);
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
DwHeaders& KMMessage::headers() const
|
|
{
|
|
return mMsg->Headers();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setNeedsAssembly()
|
|
{
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QCString KMMessage::body() const
|
|
{
|
|
const DwString& body = mMsg->Body().AsString();
|
|
QCString str = KMail::Util::CString( body );
|
|
// Calls length() -> slow
|
|
//kdWarning( str.length() != body.length(), 5006 )
|
|
// << "KMMessage::body(): body is binary but used as text!" << endl;
|
|
return str;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QByteArray KMMessage::bodyDecodedBinary() const
|
|
{
|
|
DwString dwstr;
|
|
const DwString& dwsrc = mMsg->Body().AsString();
|
|
|
|
switch (cte())
|
|
{
|
|
case DwMime::kCteBase64:
|
|
DwDecodeBase64(dwsrc, dwstr);
|
|
break;
|
|
case DwMime::kCteQuotedPrintable:
|
|
DwDecodeQuotedPrintable(dwsrc, dwstr);
|
|
break;
|
|
default:
|
|
dwstr = dwsrc;
|
|
break;
|
|
}
|
|
|
|
int len = dwstr.size();
|
|
QByteArray ba(len);
|
|
memcpy(ba.data(),dwstr.data(),len);
|
|
return ba;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QCString KMMessage::bodyDecoded() const
|
|
{
|
|
DwString dwstr;
|
|
DwString dwsrc = mMsg->Body().AsString();
|
|
|
|
switch (cte())
|
|
{
|
|
case DwMime::kCteBase64:
|
|
DwDecodeBase64(dwsrc, dwstr);
|
|
break;
|
|
case DwMime::kCteQuotedPrintable:
|
|
DwDecodeQuotedPrintable(dwsrc, dwstr);
|
|
break;
|
|
default:
|
|
dwstr = dwsrc;
|
|
break;
|
|
}
|
|
|
|
return KMail::Util::CString( dwstr );
|
|
|
|
// Calling QCString::length() is slow
|
|
//QCString result = KMail::Util::CString( dwstr );
|
|
//kdWarning(result.length() != len, 5006)
|
|
// << "KMMessage::bodyDecoded(): body is binary but used as text!" << endl;
|
|
//return result;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QValueList<int> KMMessage::determineAllowedCtes( const CharFreq& cf,
|
|
bool allow8Bit,
|
|
bool willBeSigned )
|
|
{
|
|
QValueList<int> allowedCtes;
|
|
|
|
switch ( cf.type() ) {
|
|
case CharFreq::SevenBitText:
|
|
allowedCtes << DwMime::kCte7bit;
|
|
case CharFreq::EightBitText:
|
|
if ( allow8Bit )
|
|
allowedCtes << DwMime::kCte8bit;
|
|
case CharFreq::SevenBitData:
|
|
if ( cf.printableRatio() > 5.0/6.0 ) {
|
|
// let n the length of data and p the number of printable chars.
|
|
// Then base64 \approx 4n/3; qp \approx p + 3(n-p)
|
|
// => qp < base64 iff p > 5n/6.
|
|
allowedCtes << DwMime::kCteQp;
|
|
allowedCtes << DwMime::kCteBase64;
|
|
} else {
|
|
allowedCtes << DwMime::kCteBase64;
|
|
allowedCtes << DwMime::kCteQp;
|
|
}
|
|
break;
|
|
case CharFreq::EightBitData:
|
|
allowedCtes << DwMime::kCteBase64;
|
|
break;
|
|
case CharFreq::None:
|
|
default:
|
|
// just nothing (avoid compiler warning)
|
|
;
|
|
}
|
|
|
|
// In the following cases only QP and Base64 are allowed:
|
|
// - the buffer will be OpenPGP/MIME signed and it contains trailing
|
|
// whitespace (cf. RFC 3156)
|
|
// - a line starts with "From "
|
|
if ( ( willBeSigned && cf.hasTrailingWhitespace() ) ||
|
|
cf.hasLeadingFrom() ) {
|
|
allowedCtes.remove( DwMime::kCte8bit );
|
|
allowedCtes.remove( DwMime::kCte7bit );
|
|
}
|
|
|
|
return allowedCtes;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setBodyAndGuessCte( const QByteArray& aBuf,
|
|
QValueList<int> & allowedCte,
|
|
bool allow8Bit,
|
|
bool willBeSigned )
|
|
{
|
|
CharFreq cf( aBuf ); // it's safe to pass null arrays
|
|
|
|
allowedCte = determineAllowedCtes( cf, allow8Bit, willBeSigned );
|
|
|
|
#ifndef NDEBUG
|
|
DwString dwCte;
|
|
DwCteEnumToStr(allowedCte[0], dwCte);
|
|
kdDebug(5006) << "CharFreq returned " << cf.type() << "/"
|
|
<< cf.printableRatio() << " and I chose "
|
|
<< dwCte.c_str() << endl;
|
|
#endif
|
|
|
|
setCte( allowedCte[0] ); // choose best fitting
|
|
setBodyEncodedBinary( aBuf );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setBodyAndGuessCte( const QCString& aBuf,
|
|
QValueList<int> & allowedCte,
|
|
bool allow8Bit,
|
|
bool willBeSigned )
|
|
{
|
|
CharFreq cf( aBuf.data(), aBuf.size()-1 ); // it's safe to pass null strings
|
|
|
|
allowedCte = determineAllowedCtes( cf, allow8Bit, willBeSigned );
|
|
|
|
#ifndef NDEBUG
|
|
DwString dwCte;
|
|
DwCteEnumToStr(allowedCte[0], dwCte);
|
|
kdDebug(5006) << "CharFreq returned " << cf.type() << "/"
|
|
<< cf.printableRatio() << " and I chose "
|
|
<< dwCte.c_str() << endl;
|
|
#endif
|
|
|
|
setCte( allowedCte[0] ); // choose best fitting
|
|
setBodyEncoded( aBuf );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setBodyEncoded(const QCString& aStr)
|
|
{
|
|
DwString dwSrc(aStr.data(), aStr.size()-1 /* not the trailing NUL */);
|
|
DwString dwResult;
|
|
|
|
switch (cte())
|
|
{
|
|
case DwMime::kCteBase64:
|
|
DwEncodeBase64(dwSrc, dwResult);
|
|
break;
|
|
case DwMime::kCteQuotedPrintable:
|
|
DwEncodeQuotedPrintable(dwSrc, dwResult);
|
|
break;
|
|
default:
|
|
dwResult = dwSrc;
|
|
break;
|
|
}
|
|
|
|
mMsg->Body().FromString(dwResult);
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setBodyEncodedBinary(const QByteArray& aStr)
|
|
{
|
|
DwString dwSrc(aStr.data(), aStr.size());
|
|
DwString dwResult;
|
|
|
|
switch (cte())
|
|
{
|
|
case DwMime::kCteBase64:
|
|
DwEncodeBase64(dwSrc, dwResult);
|
|
break;
|
|
case DwMime::kCteQuotedPrintable:
|
|
DwEncodeQuotedPrintable(dwSrc, dwResult);
|
|
break;
|
|
default:
|
|
dwResult = dwSrc;
|
|
break;
|
|
}
|
|
|
|
mMsg->Body().FromString(dwResult);
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setBody(const QCString& aStr)
|
|
{
|
|
mMsg->Body().FromString(KMail::Util::dwString(aStr));
|
|
mNeedsAssembly = true;
|
|
}
|
|
void KMMessage::setBody(const DwString& aStr)
|
|
{
|
|
mMsg->Body().FromString(aStr);
|
|
mNeedsAssembly = true;
|
|
}
|
|
void KMMessage::setBody(const char* aStr)
|
|
{
|
|
mMsg->Body().FromString(aStr);
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
void KMMessage::setMultiPartBody( const QCString & aStr ) {
|
|
setBody( aStr );
|
|
mMsg->Body().Parse();
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
|
|
// Patched by Daniel Moisset <dmoisset@grulic.org.ar>
|
|
// modified numbodyparts, bodypart to take nested body parts as
|
|
// a linear sequence.
|
|
// third revision, Sep 26 2000
|
|
|
|
// this is support structure for traversing tree without recursion
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int KMMessage::numBodyParts() const
|
|
{
|
|
int count = 0;
|
|
DwBodyPart* part = getFirstDwBodyPart();
|
|
QPtrList< DwBodyPart > parts;
|
|
|
|
while (part)
|
|
{
|
|
//dive into multipart messages
|
|
while ( part
|
|
&& part->hasHeaders()
|
|
&& part->Headers().HasContentType()
|
|
&& part->Body().FirstBodyPart()
|
|
&& (DwMime::kTypeMultipart == part->Headers().ContentType().Type()) )
|
|
{
|
|
parts.append( part );
|
|
part = part->Body().FirstBodyPart();
|
|
}
|
|
// this is where currPart->msgPart contains a leaf message part
|
|
count++;
|
|
// go up in the tree until reaching a node with next
|
|
// (or the last top-level node)
|
|
while (part && !(part->Next()) && !(parts.isEmpty()))
|
|
{
|
|
part = parts.getLast();
|
|
parts.removeLast();
|
|
}
|
|
|
|
if (part && part->Body().Message() &&
|
|
part->Body().Message()->Body().FirstBodyPart())
|
|
{
|
|
part = part->Body().Message()->Body().FirstBodyPart();
|
|
} else if (part) {
|
|
part = part->Next();
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
DwBodyPart * KMMessage::getFirstDwBodyPart() const
|
|
{
|
|
return mMsg->Body().FirstBodyPart();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int KMMessage::partNumber( DwBodyPart * aDwBodyPart ) const
|
|
{
|
|
DwBodyPart *curpart;
|
|
QPtrList< DwBodyPart > parts;
|
|
int curIdx = 0;
|
|
int idx = 0;
|
|
// Get the DwBodyPart for this index
|
|
|
|
curpart = getFirstDwBodyPart();
|
|
|
|
while (curpart && !idx) {
|
|
//dive into multipart messages
|
|
while( curpart
|
|
&& curpart->hasHeaders()
|
|
&& curpart->Headers().HasContentType()
|
|
&& curpart->Body().FirstBodyPart()
|
|
&& (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) )
|
|
{
|
|
parts.append( curpart );
|
|
curpart = curpart->Body().FirstBodyPart();
|
|
}
|
|
// this is where currPart->msgPart contains a leaf message part
|
|
if (curpart == aDwBodyPart)
|
|
idx = curIdx;
|
|
curIdx++;
|
|
// go up in the tree until reaching a node with next
|
|
// (or the last top-level node)
|
|
while (curpart && !(curpart->Next()) && !(parts.isEmpty()))
|
|
{
|
|
curpart = parts.getLast();
|
|
parts.removeLast();
|
|
} ;
|
|
if (curpart)
|
|
curpart = curpart->Next();
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
DwBodyPart * KMMessage::dwBodyPart( int aIdx ) const
|
|
{
|
|
DwBodyPart *part, *curpart;
|
|
QPtrList< DwBodyPart > parts;
|
|
int curIdx = 0;
|
|
// Get the DwBodyPart for this index
|
|
|
|
curpart = getFirstDwBodyPart();
|
|
part = 0;
|
|
|
|
while (curpart && !part) {
|
|
//dive into multipart messages
|
|
while( curpart
|
|
&& curpart->hasHeaders()
|
|
&& curpart->Headers().HasContentType()
|
|
&& curpart->Body().FirstBodyPart()
|
|
&& (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) )
|
|
{
|
|
parts.append( curpart );
|
|
curpart = curpart->Body().FirstBodyPart();
|
|
}
|
|
// this is where currPart->msgPart contains a leaf message part
|
|
if (curIdx==aIdx)
|
|
part = curpart;
|
|
curIdx++;
|
|
// go up in the tree until reaching a node with next
|
|
// (or the last top-level node)
|
|
while (curpart && !(curpart->Next()) && !(parts.isEmpty()))
|
|
{
|
|
curpart = parts.getLast();
|
|
parts.removeLast();
|
|
}
|
|
if (curpart)
|
|
curpart = curpart->Next();
|
|
}
|
|
return part;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
DwBodyPart * KMMessage::findDwBodyPart( int type, int subtype ) const
|
|
{
|
|
DwBodyPart *part, *curpart;
|
|
QPtrList< DwBodyPart > parts;
|
|
// Get the DwBodyPart for this index
|
|
|
|
curpart = getFirstDwBodyPart();
|
|
part = 0;
|
|
|
|
while (curpart && !part) {
|
|
//dive into multipart messages
|
|
while(curpart
|
|
&& curpart->hasHeaders()
|
|
&& curpart->Headers().HasContentType()
|
|
&& curpart->Body().FirstBodyPart()
|
|
&& (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) ) {
|
|
parts.append( curpart );
|
|
curpart = curpart->Body().FirstBodyPart();
|
|
}
|
|
// this is where curPart->msgPart contains a leaf message part
|
|
|
|
// pending(khz): Find out WHY this look does not travel down *into* an
|
|
// embedded "Message/RfF822" message containing a "Multipart/Mixed"
|
|
if ( curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() ) {
|
|
kdDebug(5006) << curpart->Headers().ContentType().TypeStr().c_str()
|
|
<< " " << curpart->Headers().ContentType().SubtypeStr().c_str() << endl;
|
|
}
|
|
|
|
if (curpart &&
|
|
curpart->hasHeaders() &&
|
|
curpart->Headers().HasContentType() &&
|
|
curpart->Headers().ContentType().Type() == type &&
|
|
curpart->Headers().ContentType().Subtype() == subtype) {
|
|
part = curpart;
|
|
} else {
|
|
// go up in the tree until reaching a node with next
|
|
// (or the last top-level node)
|
|
while (curpart && !(curpart->Next()) && !(parts.isEmpty())) {
|
|
curpart = parts.getLast();
|
|
parts.removeLast();
|
|
} ;
|
|
if (curpart)
|
|
curpart = curpart->Next();
|
|
}
|
|
}
|
|
return part;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
DwBodyPart * KMMessage::findDwBodyPart( const QCString& type, const QCString& subtype ) const
|
|
{
|
|
DwBodyPart *part, *curpart;
|
|
QPtrList< DwBodyPart > parts;
|
|
// Get the DwBodyPart for this index
|
|
|
|
curpart = getFirstDwBodyPart();
|
|
part = 0;
|
|
|
|
while (curpart && !part) {
|
|
//dive into multipart messages
|
|
while(curpart
|
|
&& curpart->hasHeaders()
|
|
&& curpart->Headers().HasContentType()
|
|
&& curpart->Body().FirstBodyPart()
|
|
&& (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) ) {
|
|
parts.append( curpart );
|
|
curpart = curpart->Body().FirstBodyPart();
|
|
}
|
|
// this is where curPart->msgPart contains a leaf message part
|
|
|
|
// pending(khz): Find out WHY this look does not travel down *into* an
|
|
// embedded "Message/RfF822" message containing a "Multipart/Mixed"
|
|
if (curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() ) {
|
|
kdDebug(5006) << curpart->Headers().ContentType().TypeStr().c_str()
|
|
<< " " << curpart->Headers().ContentType().SubtypeStr().c_str() << endl;
|
|
}
|
|
|
|
if (curpart &&
|
|
curpart->hasHeaders() &&
|
|
curpart->Headers().HasContentType() &&
|
|
curpart->Headers().ContentType().TypeStr().c_str() == type &&
|
|
curpart->Headers().ContentType().SubtypeStr().c_str() == subtype) {
|
|
part = curpart;
|
|
} else {
|
|
// go up in the tree until reaching a node with next
|
|
// (or the last top-level node)
|
|
while (curpart && !(curpart->Next()) && !(parts.isEmpty())) {
|
|
curpart = parts.getLast();
|
|
parts.removeLast();
|
|
} ;
|
|
if (curpart)
|
|
curpart = curpart->Next();
|
|
}
|
|
}
|
|
return part;
|
|
}
|
|
|
|
|
|
void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart )
|
|
{
|
|
// TODO: Instead of manually implementing RFC2231 header encoding (i.e.
|
|
// possibly multiple values given as paramname*0=..; parmaname*1=..;...
|
|
// or par as paramname*0*=..; parmaname*1*=..;..., which should be
|
|
// concatenated), use a generic method to decode the header, using RFC
|
|
// 2047 or 2231, or whatever future RFC might be appropriate!
|
|
// Right now, some fields are decoded, while others are not. E.g.
|
|
// Content-Disposition is not decoded here, rather only on demand in
|
|
// KMMsgPart::fileName; Name however is decoded here and stored as a
|
|
// decoded String in KMMsgPart...
|
|
// Content-type
|
|
QCString additionalCTypeParams;
|
|
if (headers.HasContentType())
|
|
{
|
|
DwMediaType& ct = headers.ContentType();
|
|
aPart->setOriginalContentTypeStr( ct.AsString().c_str() );
|
|
aPart->setTypeStr(ct.TypeStr().c_str());
|
|
aPart->setSubtypeStr(ct.SubtypeStr().c_str());
|
|
DwParameter *param = ct.FirstParameter();
|
|
while(param)
|
|
{
|
|
if (!qstricmp(param->Attribute().c_str(), "charset"))
|
|
aPart->setCharset(QCString(param->Value().c_str()).lower());
|
|
else if (!qstrnicmp(param->Attribute().c_str(), "name*", 5))
|
|
aPart->setName(KMMsgBase::decodeRFC2231String(KMMsgBase::extractRFC2231HeaderField( param->Value().c_str(), "name" )));
|
|
else {
|
|
additionalCTypeParams += ';';
|
|
additionalCTypeParams += param->AsString().c_str();
|
|
}
|
|
param=param->Next();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
aPart->setTypeStr("text"); // Set to defaults
|
|
aPart->setSubtypeStr("plain");
|
|
}
|
|
aPart->setAdditionalCTypeParamStr( additionalCTypeParams );
|
|
// Modification by Markus
|
|
if (aPart->name().isEmpty())
|
|
{
|
|
if (headers.HasContentType() && !headers.ContentType().Name().empty()) {
|
|
aPart->setName(KMMsgBase::decodeRFC2047String(headers.
|
|
ContentType().Name().c_str()) );
|
|
} else if (headers.HasSubject() && !headers.Subject().AsString().empty()) {
|
|
aPart->setName( KMMsgBase::decodeRFC2047String(headers.
|
|
Subject().AsString().c_str()) );
|
|
}
|
|
}
|
|
|
|
// Content-transfer-encoding
|
|
if (headers.HasContentTransferEncoding())
|
|
aPart->setCteStr(headers.ContentTransferEncoding().AsString().c_str());
|
|
else
|
|
aPart->setCteStr("7bit");
|
|
|
|
// Content-description
|
|
if (headers.HasContentDescription())
|
|
aPart->setContentDescription(headers.ContentDescription().AsString().c_str());
|
|
else
|
|
aPart->setContentDescription("");
|
|
|
|
// Content-disposition
|
|
if (headers.HasContentDisposition())
|
|
aPart->setContentDisposition(headers.ContentDisposition().AsString().c_str());
|
|
else
|
|
aPart->setContentDisposition("");
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::bodyPart(DwBodyPart* aDwBodyPart, KMMessagePart* aPart,
|
|
bool withBody)
|
|
{
|
|
if ( !aPart )
|
|
return;
|
|
|
|
aPart->clear();
|
|
|
|
if( aDwBodyPart && aDwBodyPart->hasHeaders() ) {
|
|
// This must not be an empty string, because we'll get a
|
|
// spurious empty Subject: line in some of the parts.
|
|
//aPart->setName(" ");
|
|
// partSpecifier
|
|
QString partId( aDwBodyPart->partId() );
|
|
aPart->setPartSpecifier( partId );
|
|
|
|
DwHeaders& headers = aDwBodyPart->Headers();
|
|
applyHeadersToMessagePart( headers, aPart );
|
|
|
|
// Body
|
|
if (withBody)
|
|
aPart->setBody( aDwBodyPart->Body().AsString() );
|
|
else
|
|
aPart->setBody( QCString("") );
|
|
|
|
// Content-id
|
|
if ( headers.HasContentId() ) {
|
|
const QCString contentId = headers.ContentId().AsString().c_str();
|
|
// ignore leading '<' and trailing '>'
|
|
aPart->setContentId( contentId.mid( 1, contentId.length() - 2 ) );
|
|
}
|
|
}
|
|
// If no valid body part was given,
|
|
// set all MultipartBodyPart attributes to empty values.
|
|
else
|
|
{
|
|
aPart->setTypeStr("");
|
|
aPart->setSubtypeStr("");
|
|
aPart->setCteStr("");
|
|
// This must not be an empty string, because we'll get a
|
|
// spurious empty Subject: line in some of the parts.
|
|
//aPart->setName(" ");
|
|
aPart->setContentDescription("");
|
|
aPart->setContentDisposition("");
|
|
aPart->setBody(QCString(""));
|
|
aPart->setContentId("");
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::bodyPart(int aIdx, KMMessagePart* aPart) const
|
|
{
|
|
if ( !aPart )
|
|
return;
|
|
|
|
// If the DwBodyPart was found get the header fields and body
|
|
if ( DwBodyPart *part = dwBodyPart( aIdx ) ) {
|
|
KMMessage::bodyPart(part, aPart);
|
|
if( aPart->name().isEmpty() )
|
|
aPart->setName( i18n("Attachment: %1").arg( aIdx ) );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::deleteBodyParts()
|
|
{
|
|
mMsg->Body().DeleteBodyParts();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
DwBodyPart* KMMessage::createDWBodyPart(const KMMessagePart* aPart)
|
|
{
|
|
DwBodyPart* part = DwBodyPart::NewBodyPart(emptyString, 0);
|
|
|
|
if ( !aPart )
|
|
return part;
|
|
|
|
QCString charset = aPart->charset();
|
|
QCString type = aPart->typeStr();
|
|
QCString subtype = aPart->subtypeStr();
|
|
QCString cte = aPart->cteStr();
|
|
QCString contDesc = aPart->contentDescriptionEncoded();
|
|
QCString contDisp = aPart->contentDisposition();
|
|
QCString encoding = autoDetectCharset(charset, sPrefCharsets, aPart->name());
|
|
if (encoding.isEmpty()) encoding = "utf-8";
|
|
QCString name = KMMsgBase::encodeRFC2231String(aPart->name(), encoding);
|
|
bool RFC2231encoded = aPart->name() != QString(name);
|
|
QCString paramAttr = aPart->parameterAttribute();
|
|
|
|
DwHeaders& headers = part->Headers();
|
|
|
|
DwMediaType& ct = headers.ContentType();
|
|
if (!type.isEmpty() && !subtype.isEmpty())
|
|
{
|
|
ct.SetTypeStr(type.data());
|
|
ct.SetSubtypeStr(subtype.data());
|
|
if (!charset.isEmpty()){
|
|
DwParameter *param;
|
|
param=new DwParameter;
|
|
param->SetAttribute("charset");
|
|
param->SetValue(charset.data());
|
|
ct.AddParameter(param);
|
|
}
|
|
}
|
|
|
|
QCString additionalParam = aPart->additionalCTypeParamStr();
|
|
if( !additionalParam.isEmpty() )
|
|
{
|
|
QCString parAV;
|
|
DwString parA, parV;
|
|
int iL, i1, i2, iM;
|
|
iL = additionalParam.length();
|
|
i1 = 0;
|
|
i2 = additionalParam.find(';', i1, false);
|
|
while ( i1 < iL )
|
|
{
|
|
if( -1 == i2 )
|
|
i2 = iL;
|
|
if( i1+1 < i2 ) {
|
|
parAV = additionalParam.mid( i1, (i2-i1) );
|
|
iM = parAV.find('=');
|
|
if( -1 < iM )
|
|
{
|
|
parA = parAV.left( iM );
|
|
parV = parAV.right( parAV.length() - iM - 1 );
|
|
if( ('"' == parV.at(0)) && ('"' == parV.at(parV.length()-1)) )
|
|
{
|
|
parV.erase( 0, 1);
|
|
parV.erase( parV.length()-1 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
parA = parAV;
|
|
parV = "";
|
|
}
|
|
DwParameter *param;
|
|
param = new DwParameter;
|
|
param->SetAttribute( parA );
|
|
param->SetValue( parV );
|
|
ct.AddParameter( param );
|
|
}
|
|
i1 = i2+1;
|
|
i2 = additionalParam.find(';', i1, false);
|
|
}
|
|
}
|
|
|
|
if ( !name.isEmpty() ) {
|
|
if (RFC2231encoded)
|
|
{
|
|
DwParameter *nameParam;
|
|
nameParam = new DwParameter;
|
|
nameParam->SetAttribute("name*");
|
|
nameParam->SetValue(name.data(),true);
|
|
ct.AddParameter(nameParam);
|
|
} else {
|
|
ct.SetName(name.data());
|
|
}
|
|
}
|
|
|
|
if (!paramAttr.isEmpty())
|
|
{
|
|
QCString encoding = autoDetectCharset(charset, sPrefCharsets,
|
|
aPart->parameterValue());
|
|
if (encoding.isEmpty()) encoding = "utf-8";
|
|
QCString paramValue;
|
|
paramValue = KMMsgBase::encodeRFC2231String(aPart->parameterValue(),
|
|
encoding);
|
|
DwParameter *param = new DwParameter;
|
|
if (aPart->parameterValue() != QString(paramValue))
|
|
{
|
|
param->SetAttribute((paramAttr + '*').data());
|
|
param->SetValue(paramValue.data(),true);
|
|
} else {
|
|
param->SetAttribute(paramAttr.data());
|
|
param->SetValue(paramValue.data());
|
|
}
|
|
ct.AddParameter(param);
|
|
}
|
|
|
|
if (!cte.isEmpty())
|
|
headers.Cte().FromString(cte);
|
|
|
|
if (!contDesc.isEmpty())
|
|
headers.ContentDescription().FromString(contDesc);
|
|
|
|
if (!contDisp.isEmpty())
|
|
headers.ContentDisposition().FromString(contDisp);
|
|
|
|
const DwString bodyStr = aPart->dwBody();
|
|
if (!bodyStr.empty())
|
|
part->Body().FromString(bodyStr);
|
|
else
|
|
part->Body().FromString("");
|
|
|
|
if (!aPart->partSpecifier().isNull())
|
|
part->SetPartId( aPart->partSpecifier().latin1() );
|
|
|
|
if (aPart->decodedSize() > 0)
|
|
part->SetBodySize( aPart->decodedSize() );
|
|
|
|
return part;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::addDwBodyPart(DwBodyPart * aDwPart)
|
|
{
|
|
mMsg->Body().AddBodyPart( aDwPart );
|
|
mNeedsAssembly = true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::addBodyPart(const KMMessagePart* aPart)
|
|
{
|
|
DwBodyPart* part = createDWBodyPart( aPart );
|
|
addDwBodyPart( part );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::generateMessageId( const QString& addr )
|
|
{
|
|
QDateTime datetime = QDateTime::currentDateTime();
|
|
QString msgIdStr;
|
|
|
|
msgIdStr = '<' + datetime.toString( "yyyyMMddhhmm.sszzz" );
|
|
|
|
QString msgIdSuffix;
|
|
KConfigGroup general( KMKernel::config(), "General" );
|
|
|
|
if( general.readBoolEntry( "useCustomMessageIdSuffix", false ) )
|
|
msgIdSuffix = general.readEntry( "myMessageIdSuffix" );
|
|
|
|
if( !msgIdSuffix.isEmpty() )
|
|
msgIdStr += '@' + msgIdSuffix;
|
|
else
|
|
msgIdStr += '.' + KPIM::encodeIDN( addr );
|
|
|
|
msgIdStr += '>';
|
|
|
|
return msgIdStr;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QCString KMMessage::html2source( const QCString & src )
|
|
{
|
|
QCString result( 1 + 6*(src.size()-1) ); // maximal possible length
|
|
|
|
QCString::ConstIterator s = src.begin();
|
|
QCString::Iterator d = result.begin();
|
|
while ( *s ) {
|
|
switch ( *s ) {
|
|
case '<': {
|
|
*d++ = '&';
|
|
*d++ = 'l';
|
|
*d++ = 't';
|
|
*d++ = ';';
|
|
++s;
|
|
}
|
|
break;
|
|
case '\r': {
|
|
++s;
|
|
}
|
|
break;
|
|
case '\n': {
|
|
*d++ = '<';
|
|
*d++ = 'b';
|
|
*d++ = 'r';
|
|
*d++ = '>';
|
|
++s;
|
|
}
|
|
break;
|
|
case '>': {
|
|
*d++ = '&';
|
|
*d++ = 'g';
|
|
*d++ = 't';
|
|
*d++ = ';';
|
|
++s;
|
|
}
|
|
break;
|
|
case '&': {
|
|
*d++ = '&';
|
|
*d++ = 'a';
|
|
*d++ = 'm';
|
|
*d++ = 'p';
|
|
*d++ = ';';
|
|
++s;
|
|
}
|
|
break;
|
|
case '"': {
|
|
*d++ = '&';
|
|
*d++ = 'q';
|
|
*d++ = 'u';
|
|
*d++ = 'o';
|
|
*d++ = 't';
|
|
*d++ = ';';
|
|
++s;
|
|
}
|
|
break;
|
|
case '\'': {
|
|
*d++ = '&';
|
|
*d++ = 'a';
|
|
*d++ = 'p';
|
|
*d++ = 's';
|
|
*d++ = ';';
|
|
++s;
|
|
}
|
|
break;
|
|
default:
|
|
*d++ = *s++;
|
|
}
|
|
}
|
|
result.truncate( d - result.begin() ); // adds trailing NUL
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::encodeMailtoUrl( const QString& str )
|
|
{
|
|
QString result;
|
|
result = QString::fromLatin1( KMMsgBase::encodeRFC2047String( str,
|
|
"utf-8" ) );
|
|
result = KURL::encode_string( result );
|
|
return result;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::decodeMailtoUrl( const QString& url )
|
|
{
|
|
QString result;
|
|
result = KURL::decode_string( url );
|
|
result = KMMsgBase::decodeRFC2047String( result.latin1() );
|
|
return result;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QCString KMMessage::stripEmailAddr( const QCString& aStr )
|
|
{
|
|
//kdDebug(5006) << "KMMessage::stripEmailAddr( " << aStr << " )" << endl;
|
|
|
|
if ( aStr.isEmpty() )
|
|
return QCString();
|
|
|
|
QCString result;
|
|
|
|
// The following is a primitive parser for a mailbox-list (cf. RFC 2822).
|
|
// The purpose is to extract a displayable string from the mailboxes.
|
|
// Comments in the addr-spec are not handled. No error checking is done.
|
|
|
|
QCString name;
|
|
QCString comment;
|
|
QCString angleAddress;
|
|
enum { TopLevel, InComment, InAngleAddress } context = TopLevel;
|
|
bool inQuotedString = false;
|
|
int commentLevel = 0;
|
|
|
|
for ( char* p = aStr.data(); *p; ++p ) {
|
|
switch ( context ) {
|
|
case TopLevel : {
|
|
switch ( *p ) {
|
|
case '"' : inQuotedString = !inQuotedString;
|
|
break;
|
|
case '(' : if ( !inQuotedString ) {
|
|
context = InComment;
|
|
commentLevel = 1;
|
|
}
|
|
else
|
|
name += *p;
|
|
break;
|
|
case '<' : if ( !inQuotedString ) {
|
|
context = InAngleAddress;
|
|
}
|
|
else
|
|
name += *p;
|
|
break;
|
|
case '\\' : // quoted character
|
|
++p; // skip the '\'
|
|
if ( *p )
|
|
name += *p;
|
|
break;
|
|
case ',' : if ( !inQuotedString ) {
|
|
// next email address
|
|
if ( !result.isEmpty() )
|
|
result += ", ";
|
|
name = name.stripWhiteSpace();
|
|
comment = comment.stripWhiteSpace();
|
|
angleAddress = angleAddress.stripWhiteSpace();
|
|
/*
|
|
kdDebug(5006) << "Name : \"" << name
|
|
<< "\"" << endl;
|
|
kdDebug(5006) << "Comment : \"" << comment
|
|
<< "\"" << endl;
|
|
kdDebug(5006) << "Address : \"" << angleAddress
|
|
<< "\"" << endl;
|
|
*/
|
|
if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
|
|
// handle Outlook-style addresses like
|
|
// john.doe@invalid (John Doe)
|
|
result += comment;
|
|
}
|
|
else if ( !name.isEmpty() ) {
|
|
result += name;
|
|
}
|
|
else if ( !comment.isEmpty() ) {
|
|
result += comment;
|
|
}
|
|
else if ( !angleAddress.isEmpty() ) {
|
|
result += angleAddress;
|
|
}
|
|
name = QCString();
|
|
comment = QCString();
|
|
angleAddress = QCString();
|
|
}
|
|
else
|
|
name += *p;
|
|
break;
|
|
default : name += *p;
|
|
}
|
|
break;
|
|
}
|
|
case InComment : {
|
|
switch ( *p ) {
|
|
case '(' : ++commentLevel;
|
|
comment += *p;
|
|
break;
|
|
case ')' : --commentLevel;
|
|
if ( commentLevel == 0 ) {
|
|
context = TopLevel;
|
|
comment += ' '; // separate the text of several comments
|
|
}
|
|
else
|
|
comment += *p;
|
|
break;
|
|
case '\\' : // quoted character
|
|
++p; // skip the '\'
|
|
if ( *p )
|
|
comment += *p;
|
|
break;
|
|
default : comment += *p;
|
|
}
|
|
break;
|
|
}
|
|
case InAngleAddress : {
|
|
switch ( *p ) {
|
|
case '"' : inQuotedString = !inQuotedString;
|
|
angleAddress += *p;
|
|
break;
|
|
case '>' : if ( !inQuotedString ) {
|
|
context = TopLevel;
|
|
}
|
|
else
|
|
angleAddress += *p;
|
|
break;
|
|
case '\\' : // quoted character
|
|
++p; // skip the '\'
|
|
if ( *p )
|
|
angleAddress += *p;
|
|
break;
|
|
default : angleAddress += *p;
|
|
}
|
|
break;
|
|
}
|
|
} // switch ( context )
|
|
}
|
|
if ( !result.isEmpty() )
|
|
result += ", ";
|
|
name = name.stripWhiteSpace();
|
|
comment = comment.stripWhiteSpace();
|
|
angleAddress = angleAddress.stripWhiteSpace();
|
|
/*
|
|
kdDebug(5006) << "Name : \"" << name << "\"" << endl;
|
|
kdDebug(5006) << "Comment : \"" << comment << "\"" << endl;
|
|
kdDebug(5006) << "Address : \"" << angleAddress << "\"" << endl;
|
|
*/
|
|
if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
|
|
// handle Outlook-style addresses like
|
|
// john.doe@invalid (John Doe)
|
|
result += comment;
|
|
}
|
|
else if ( !name.isEmpty() ) {
|
|
result += name;
|
|
}
|
|
else if ( !comment.isEmpty() ) {
|
|
result += comment;
|
|
}
|
|
else if ( !angleAddress.isEmpty() ) {
|
|
result += angleAddress;
|
|
}
|
|
|
|
//kdDebug(5006) << "KMMessage::stripEmailAddr(...) returns \"" << result
|
|
// << "\"" << endl;
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::stripEmailAddr( const QString& aStr )
|
|
{
|
|
//kdDebug(5006) << "KMMessage::stripEmailAddr( " << aStr << " )" << endl;
|
|
|
|
if ( aStr.isEmpty() )
|
|
return QString::null;
|
|
|
|
QString result;
|
|
|
|
// The following is a primitive parser for a mailbox-list (cf. RFC 2822).
|
|
// The purpose is to extract a displayable string from the mailboxes.
|
|
// Comments in the addr-spec are not handled. No error checking is done.
|
|
|
|
QString name;
|
|
QString comment;
|
|
QString angleAddress;
|
|
enum { TopLevel, InComment, InAngleAddress } context = TopLevel;
|
|
bool inQuotedString = false;
|
|
int commentLevel = 0;
|
|
|
|
QChar ch;
|
|
unsigned int strLength(aStr.length());
|
|
for ( uint index = 0; index < strLength; ++index ) {
|
|
ch = aStr[index];
|
|
switch ( context ) {
|
|
case TopLevel : {
|
|
switch ( ch.latin1() ) {
|
|
case '"' : inQuotedString = !inQuotedString;
|
|
break;
|
|
case '(' : if ( !inQuotedString ) {
|
|
context = InComment;
|
|
commentLevel = 1;
|
|
}
|
|
else
|
|
name += ch;
|
|
break;
|
|
case '<' : if ( !inQuotedString ) {
|
|
context = InAngleAddress;
|
|
}
|
|
else
|
|
name += ch;
|
|
break;
|
|
case '\\' : // quoted character
|
|
++index; // skip the '\'
|
|
if ( index < aStr.length() )
|
|
name += aStr[index];
|
|
break;
|
|
case ',' : if ( !inQuotedString ) {
|
|
// next email address
|
|
if ( !result.isEmpty() )
|
|
result += ", ";
|
|
name = name.stripWhiteSpace();
|
|
comment = comment.stripWhiteSpace();
|
|
angleAddress = angleAddress.stripWhiteSpace();
|
|
/*
|
|
kdDebug(5006) << "Name : \"" << name
|
|
<< "\"" << endl;
|
|
kdDebug(5006) << "Comment : \"" << comment
|
|
<< "\"" << endl;
|
|
kdDebug(5006) << "Address : \"" << angleAddress
|
|
<< "\"" << endl;
|
|
*/
|
|
if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
|
|
// handle Outlook-style addresses like
|
|
// john.doe@invalid (John Doe)
|
|
result += comment;
|
|
}
|
|
else if ( !name.isEmpty() ) {
|
|
result += name;
|
|
}
|
|
else if ( !comment.isEmpty() ) {
|
|
result += comment;
|
|
}
|
|
else if ( !angleAddress.isEmpty() ) {
|
|
result += angleAddress;
|
|
}
|
|
name = QString::null;
|
|
comment = QString::null;
|
|
angleAddress = QString::null;
|
|
}
|
|
else
|
|
name += ch;
|
|
break;
|
|
default : name += ch;
|
|
}
|
|
break;
|
|
}
|
|
case InComment : {
|
|
switch ( ch.latin1() ) {
|
|
case '(' : ++commentLevel;
|
|
comment += ch;
|
|
break;
|
|
case ')' : --commentLevel;
|
|
if ( commentLevel == 0 ) {
|
|
context = TopLevel;
|
|
comment += ' '; // separate the text of several comments
|
|
}
|
|
else
|
|
comment += ch;
|
|
break;
|
|
case '\\' : // quoted character
|
|
++index; // skip the '\'
|
|
if ( index < aStr.length() )
|
|
comment += aStr[index];
|
|
break;
|
|
default : comment += ch;
|
|
}
|
|
break;
|
|
}
|
|
case InAngleAddress : {
|
|
switch ( ch.latin1() ) {
|
|
case '"' : inQuotedString = !inQuotedString;
|
|
angleAddress += ch;
|
|
break;
|
|
case '>' : if ( !inQuotedString ) {
|
|
context = TopLevel;
|
|
}
|
|
else
|
|
angleAddress += ch;
|
|
break;
|
|
case '\\' : // quoted character
|
|
++index; // skip the '\'
|
|
if ( index < aStr.length() )
|
|
angleAddress += aStr[index];
|
|
break;
|
|
default : angleAddress += ch;
|
|
}
|
|
break;
|
|
}
|
|
} // switch ( context )
|
|
}
|
|
if ( !result.isEmpty() )
|
|
result += ", ";
|
|
name = name.stripWhiteSpace();
|
|
comment = comment.stripWhiteSpace();
|
|
angleAddress = angleAddress.stripWhiteSpace();
|
|
/*
|
|
kdDebug(5006) << "Name : \"" << name << "\"" << endl;
|
|
kdDebug(5006) << "Comment : \"" << comment << "\"" << endl;
|
|
kdDebug(5006) << "Address : \"" << angleAddress << "\"" << endl;
|
|
*/
|
|
if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
|
|
// handle Outlook-style addresses like
|
|
// john.doe@invalid (John Doe)
|
|
result += comment;
|
|
}
|
|
else if ( !name.isEmpty() ) {
|
|
result += name;
|
|
}
|
|
else if ( !comment.isEmpty() ) {
|
|
result += comment;
|
|
}
|
|
else if ( !angleAddress.isEmpty() ) {
|
|
result += angleAddress;
|
|
}
|
|
|
|
//kdDebug(5006) << "KMMessage::stripEmailAddr(...) returns \"" << result
|
|
// << "\"" << endl;
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::quoteHtmlChars( const QString& str, bool removeLineBreaks )
|
|
{
|
|
QString result;
|
|
|
|
unsigned int strLength(str.length());
|
|
result.reserve( 6*strLength ); // maximal possible length
|
|
for( unsigned int i = 0; i < strLength; ++i )
|
|
switch ( str[i].latin1() ) {
|
|
case '<':
|
|
result += "<";
|
|
break;
|
|
case '>':
|
|
result += ">";
|
|
break;
|
|
case '&':
|
|
result += "&";
|
|
break;
|
|
case '"':
|
|
result += """;
|
|
break;
|
|
case '\n':
|
|
if ( !removeLineBreaks )
|
|
result += "<br>";
|
|
break;
|
|
case '\r':
|
|
// ignore CR
|
|
break;
|
|
default:
|
|
result += str[i];
|
|
}
|
|
|
|
result.squeeze();
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QString KMMessage::emailAddrAsAnchor(const QString& aEmail, bool stripped, const QString& cssStyle, bool aLink)
|
|
{
|
|
if( aEmail.isEmpty() )
|
|
return aEmail;
|
|
|
|
QStringList addressList = KPIM::splitEmailAddrList( aEmail );
|
|
|
|
QString result;
|
|
|
|
for( QStringList::ConstIterator it = addressList.begin();
|
|
( it != addressList.end() );
|
|
++it ) {
|
|
if( !(*it).isEmpty() ) {
|
|
QString address = *it;
|
|
if(aLink) {
|
|
result += "<a href=\"mailto:"
|
|
+ KMMessage::encodeMailtoUrl( address )
|
|
+ "\" "+cssStyle+">";
|
|
}
|
|
if( stripped )
|
|
address = KMMessage::stripEmailAddr( address );
|
|
result += KMMessage::quoteHtmlChars( address, true );
|
|
if(aLink)
|
|
result += "</a>, ";
|
|
}
|
|
}
|
|
// cut of the trailing ", "
|
|
if(aLink)
|
|
result.truncate( result.length() - 2 );
|
|
|
|
//kdDebug(5006) << "KMMessage::emailAddrAsAnchor('" << aEmail
|
|
// << "') returns:\n-->" << result << "<--" << endl;
|
|
return result;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//static
|
|
QStringList KMMessage::stripAddressFromAddressList( const QString& address,
|
|
const QStringList& list )
|
|
{
|
|
QStringList addresses( list );
|
|
QString addrSpec( KPIM::getEmailAddress( address ) );
|
|
for ( QStringList::Iterator it = addresses.begin();
|
|
it != addresses.end(); ) {
|
|
if ( kasciistricmp( addrSpec.utf8().data(),
|
|
KPIM::getEmailAddress( *it ).utf8().data() ) == 0 ) {
|
|
kdDebug(5006) << "Removing " << *it << " from the address list"
|
|
<< endl;
|
|
it = addresses.remove( it );
|
|
}
|
|
else
|
|
++it;
|
|
}
|
|
return addresses;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//static
|
|
QStringList KMMessage::stripMyAddressesFromAddressList( const QStringList& list )
|
|
{
|
|
QStringList addresses = list;
|
|
for( QStringList::Iterator it = addresses.begin();
|
|
it != addresses.end(); ) {
|
|
kdDebug(5006) << "Check whether " << *it << " is one of my addresses"
|
|
<< endl;
|
|
if( kmkernel->identityManager()->thatIsMe( KPIM::getEmailAddress( *it ) ) ) {
|
|
kdDebug(5006) << "Removing " << *it << " from the address list"
|
|
<< endl;
|
|
it = addresses.remove( it );
|
|
}
|
|
else
|
|
++it;
|
|
}
|
|
return addresses;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//static
|
|
bool KMMessage::addressIsInAddressList( const QString& address,
|
|
const QStringList& addresses )
|
|
{
|
|
QString addrSpec = KPIM::getEmailAddress( address );
|
|
for( QStringList::ConstIterator it = addresses.begin();
|
|
it != addresses.end(); ++it ) {
|
|
if ( kasciistricmp( addrSpec.utf8().data(),
|
|
KPIM::getEmailAddress( *it ).utf8().data() ) == 0 )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//static
|
|
QString KMMessage::expandAliases( const QString& recipients )
|
|
{
|
|
if ( recipients.isEmpty() )
|
|
return QString();
|
|
|
|
QStringList recipientList = KPIM::splitEmailAddrList( recipients );
|
|
|
|
QString expandedRecipients;
|
|
for ( QStringList::Iterator it = recipientList.begin();
|
|
it != recipientList.end(); ++it ) {
|
|
if ( !expandedRecipients.isEmpty() )
|
|
expandedRecipients += ", ";
|
|
QString receiver = (*it).stripWhiteSpace();
|
|
|
|
// try to expand distribution list
|
|
QString expandedList = KAddrBookExternal::expandDistributionList( receiver );
|
|
if ( !expandedList.isEmpty() ) {
|
|
expandedRecipients += expandedList;
|
|
continue;
|
|
}
|
|
|
|
// try to expand nick name
|
|
QString expandedNickName = KabcBridge::expandNickName( receiver );
|
|
if ( !expandedNickName.isEmpty() ) {
|
|
expandedRecipients += expandedNickName;
|
|
continue;
|
|
}
|
|
|
|
// check whether the address is missing the domain part
|
|
// FIXME: looking for '@' might be wrong
|
|
if ( receiver.find('@') == -1 ) {
|
|
KConfigGroup general( KMKernel::config(), "General" );
|
|
QString defaultdomain = general.readEntry( "Default domain" );
|
|
if( !defaultdomain.isEmpty() ) {
|
|
expandedRecipients += receiver + "@" + defaultdomain;
|
|
}
|
|
else {
|
|
expandedRecipients += guessEmailAddressFromLoginName( receiver );
|
|
}
|
|
}
|
|
else
|
|
expandedRecipients += receiver;
|
|
}
|
|
|
|
return expandedRecipients;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//static
|
|
QString KMMessage::guessEmailAddressFromLoginName( const QString& loginName )
|
|
{
|
|
if ( loginName.isEmpty() )
|
|
return QString();
|
|
|
|
char hostnameC[256];
|
|
// null terminate this C string
|
|
hostnameC[255] = '\0';
|
|
// set the string to 0 length if gethostname fails
|
|
if ( gethostname( hostnameC, 255 ) )
|
|
hostnameC[0] = '\0';
|
|
QString address = loginName;
|
|
address += '@';
|
|
address += QString::fromLocal8Bit( hostnameC );
|
|
|
|
// try to determine the real name
|
|
const KUser user( loginName );
|
|
if ( user.isValid() ) {
|
|
QString fullName = user.fullName();
|
|
if ( fullName.find( QRegExp( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ) ) != -1 )
|
|
address = '"' + fullName.replace( '\\', "\\" ).replace( '"', "\\" )
|
|
+ "\" <" + address + '>';
|
|
else
|
|
address = fullName + " <" + address + '>';
|
|
}
|
|
|
|
return address;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::readConfig()
|
|
{
|
|
KMMsgBase::readConfig();
|
|
|
|
KConfig *config=KMKernel::config();
|
|
KConfigGroupSaver saver(config, "General");
|
|
|
|
config->setGroup("General");
|
|
|
|
int languageNr = config->readNumEntry("reply-current-language",0);
|
|
|
|
{ // area for config group "KMMessage #n"
|
|
KConfigGroupSaver saver(config, QString("KMMessage #%1").arg(languageNr));
|
|
sReplyLanguage = config->readEntry("language",KGlobal::locale()->language());
|
|
sReplyStr = config->readEntry("phrase-reply",
|
|
i18n("On %D, you wrote:"));
|
|
sReplyAllStr = config->readEntry("phrase-reply-all",
|
|
i18n("On %D, %F wrote:"));
|
|
sForwardStr = config->readEntry("phrase-forward",
|
|
i18n("Forwarded Message"));
|
|
sIndentPrefixStr = config->readEntry("indent-prefix",">%_");
|
|
}
|
|
|
|
{ // area for config group "Composer"
|
|
KConfigGroupSaver saver(config, "Composer");
|
|
sSmartQuote = GlobalSettings::self()->smartQuote();
|
|
sWordWrap = GlobalSettings::self()->wordWrap();
|
|
sWrapCol = GlobalSettings::self()->lineWrapWidth();
|
|
if ((sWrapCol == 0) || (sWrapCol > 78))
|
|
sWrapCol = 78;
|
|
if (sWrapCol < 30)
|
|
sWrapCol = 30;
|
|
|
|
sPrefCharsets = config->readListEntry("pref-charsets");
|
|
}
|
|
|
|
{ // area for config group "Reader"
|
|
KConfigGroupSaver saver(config, "Reader");
|
|
sHeaderStrategy = HeaderStrategy::create( config->readEntry( "header-set-displayed", "rich" ) );
|
|
}
|
|
}
|
|
|
|
QCString KMMessage::defaultCharset()
|
|
{
|
|
QCString retval;
|
|
|
|
if (!sPrefCharsets.isEmpty())
|
|
retval = sPrefCharsets[0].latin1();
|
|
|
|
if (retval.isEmpty() || (retval == "locale")) {
|
|
retval = QCString(kmkernel->networkCodec()->mimeName());
|
|
KPIM::kAsciiToLower( retval.data() );
|
|
}
|
|
|
|
if (retval == "jisx0208.1983-0") retval = "iso-2022-jp";
|
|
else if (retval == "ksc5601.1987-0") retval = "euc-kr";
|
|
return retval;
|
|
}
|
|
|
|
const QStringList &KMMessage::preferredCharsets()
|
|
{
|
|
return sPrefCharsets;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QCString KMMessage::charset() const
|
|
{
|
|
if ( mMsg->Headers().HasContentType() ) {
|
|
DwMediaType &mType=mMsg->Headers().ContentType();
|
|
mType.Parse();
|
|
DwParameter *param=mType.FirstParameter();
|
|
while(param){
|
|
if (!kasciistricmp(param->Attribute().c_str(), "charset"))
|
|
return param->Value().c_str();
|
|
else param=param->Next();
|
|
}
|
|
}
|
|
return ""; // us-ascii, but we don't have to specify it
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setCharset(const QCString& bStr)
|
|
{
|
|
kdWarning( type() != DwMime::kTypeText )
|
|
<< "KMMessage::setCharset(): trying to set a charset for a non-textual mimetype." << endl
|
|
<< "Fix this caller:" << endl
|
|
<< "====================================================================" << endl
|
|
<< kdBacktrace( 5 ) << endl
|
|
<< "====================================================================" << endl;
|
|
QCString aStr = bStr;
|
|
KPIM::kAsciiToLower( aStr.data() );
|
|
DwMediaType &mType = dwContentType();
|
|
mType.Parse();
|
|
DwParameter *param=mType.FirstParameter();
|
|
while(param)
|
|
// FIXME use the mimelib functions here for comparison.
|
|
if (!kasciistricmp(param->Attribute().c_str(), "charset")) break;
|
|
else param=param->Next();
|
|
if (!param){
|
|
param=new DwParameter;
|
|
param->SetAttribute("charset");
|
|
mType.AddParameter(param);
|
|
}
|
|
else
|
|
mType.SetModified();
|
|
param->SetValue(DwString(aStr));
|
|
mType.Assemble();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::setStatus(const KMMsgStatus aStatus, int idx)
|
|
{
|
|
if (mStatus == aStatus)
|
|
return;
|
|
KMMsgBase::setStatus(aStatus, idx);
|
|
}
|
|
|
|
void KMMessage::setEncryptionState(const KMMsgEncryptionState s, int idx)
|
|
{
|
|
if( mEncryptionState == s )
|
|
return;
|
|
mEncryptionState = s;
|
|
mDirty = true;
|
|
KMMsgBase::setEncryptionState(s, idx);
|
|
}
|
|
|
|
void KMMessage::setSignatureState(KMMsgSignatureState s, int idx)
|
|
{
|
|
if( mSignatureState == s )
|
|
return;
|
|
mSignatureState = s;
|
|
mDirty = true;
|
|
KMMsgBase::setSignatureState(s, idx);
|
|
}
|
|
|
|
void KMMessage::setMDNSentState( KMMsgMDNSentState status, int idx ) {
|
|
if ( mMDNSentState == status )
|
|
return;
|
|
if ( status == 0 )
|
|
status = KMMsgMDNStateUnknown;
|
|
mMDNSentState = status;
|
|
mDirty = true;
|
|
KMMsgBase::setMDNSentState( status, idx );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::link( const KMMessage *aMsg, KMMsgStatus aStatus )
|
|
{
|
|
Q_ASSERT( aStatus == KMMsgStatusReplied
|
|
|| aStatus == KMMsgStatusForwarded
|
|
|| aStatus == KMMsgStatusDeleted );
|
|
|
|
QString message = headerField( "X-KMail-Link-Message" );
|
|
if ( !message.isEmpty() )
|
|
message += ',';
|
|
QString type = headerField( "X-KMail-Link-Type" );
|
|
if ( !type.isEmpty() )
|
|
type += ',';
|
|
|
|
message += QString::number( aMsg->getMsgSerNum() );
|
|
if ( aStatus == KMMsgStatusReplied )
|
|
type += "reply";
|
|
else if ( aStatus == KMMsgStatusForwarded )
|
|
type += "forward";
|
|
else if ( aStatus == KMMsgStatusDeleted )
|
|
type += "deleted";
|
|
|
|
setHeaderField( "X-KMail-Link-Message", message );
|
|
setHeaderField( "X-KMail-Link-Type", type );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::getLink(int n, ulong *retMsgSerNum, KMMsgStatus *retStatus) const
|
|
{
|
|
*retMsgSerNum = 0;
|
|
*retStatus = KMMsgStatusUnknown;
|
|
|
|
QString message = headerField("X-KMail-Link-Message");
|
|
QString type = headerField("X-KMail-Link-Type");
|
|
message = message.section(',', n, n);
|
|
type = type.section(',', n, n);
|
|
|
|
if ( !message.isEmpty() && !type.isEmpty() ) {
|
|
*retMsgSerNum = message.toULong();
|
|
if ( type == "reply" )
|
|
*retStatus = KMMsgStatusReplied;
|
|
else if ( type == "forward" )
|
|
*retStatus = KMMsgStatusForwarded;
|
|
else if ( type == "deleted" )
|
|
*retStatus = KMMsgStatusDeleted;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
DwBodyPart* KMMessage::findDwBodyPart( DwBodyPart* part, const QString & partSpecifier )
|
|
{
|
|
if ( !part ) return 0;
|
|
DwBodyPart* current;
|
|
|
|
if ( part->partId() == partSpecifier )
|
|
return part;
|
|
|
|
// multipart
|
|
if ( part->hasHeaders() &&
|
|
part->Headers().HasContentType() &&
|
|
part->Body().FirstBodyPart() &&
|
|
(DwMime::kTypeMultipart == part->Headers().ContentType().Type() ) &&
|
|
(current = findDwBodyPart( part->Body().FirstBodyPart(), partSpecifier )) )
|
|
{
|
|
return current;
|
|
}
|
|
|
|
// encapsulated message
|
|
if ( part->Body().Message() &&
|
|
part->Body().Message()->Body().FirstBodyPart() &&
|
|
(current = findDwBodyPart( part->Body().Message()->Body().FirstBodyPart(),
|
|
partSpecifier )) )
|
|
{
|
|
return current;
|
|
}
|
|
|
|
// next part
|
|
return findDwBodyPart( part->Next(), partSpecifier );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::updateBodyPart(const QString partSpecifier, const QByteArray & data)
|
|
{
|
|
if ( !data.data() || !data.size() )
|
|
return;
|
|
|
|
DwString content( data.data(), data.size() );
|
|
if ( numBodyParts() > 0 &&
|
|
partSpecifier != "0" &&
|
|
partSpecifier != "TEXT" )
|
|
{
|
|
QString specifier = partSpecifier;
|
|
if ( partSpecifier.endsWith(".HEADER") ||
|
|
partSpecifier.endsWith(".MIME") ) {
|
|
// get the parent bodypart
|
|
specifier = partSpecifier.section( '.', 0, -2 );
|
|
}
|
|
|
|
// search for the bodypart
|
|
mLastUpdated = findDwBodyPart( getFirstDwBodyPart(), specifier );
|
|
kdDebug(5006) << "KMMessage::updateBodyPart " << specifier << endl;
|
|
if (!mLastUpdated)
|
|
{
|
|
kdWarning(5006) << "KMMessage::updateBodyPart - can not find part "
|
|
<< specifier << endl;
|
|
return;
|
|
}
|
|
if ( partSpecifier.endsWith(".MIME") )
|
|
{
|
|
// update headers
|
|
// get rid of EOL
|
|
content.resize( QMAX( content.length(), 2 ) - 2 );
|
|
// we have to delete the fields first as they might have been created by
|
|
// an earlier call to DwHeaders::FieldBody
|
|
mLastUpdated->Headers().DeleteAllFields();
|
|
mLastUpdated->Headers().FromString( content );
|
|
mLastUpdated->Headers().Parse();
|
|
} else if ( partSpecifier.endsWith(".HEADER") )
|
|
{
|
|
// update header of embedded message
|
|
mLastUpdated->Body().Message()->Headers().FromString( content );
|
|
mLastUpdated->Body().Message()->Headers().Parse();
|
|
} else {
|
|
// update body
|
|
mLastUpdated->Body().FromString( content );
|
|
QString parentSpec = partSpecifier.section( '.', 0, -2 );
|
|
if ( !parentSpec.isEmpty() )
|
|
{
|
|
DwBodyPart* parent = findDwBodyPart( getFirstDwBodyPart(), parentSpec );
|
|
if ( parent && parent->hasHeaders() && parent->Headers().HasContentType() )
|
|
{
|
|
const DwMediaType& contentType = parent->Headers().ContentType();
|
|
if ( contentType.Type() == DwMime::kTypeMessage &&
|
|
contentType.Subtype() == DwMime::kSubtypeRfc822 )
|
|
{
|
|
// an embedded message that is not multipart
|
|
// update this directly
|
|
parent->Body().Message()->Body().FromString( content );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} else
|
|
{
|
|
// update text-only messages
|
|
if ( partSpecifier == "TEXT" )
|
|
deleteBodyParts(); // delete empty parts first
|
|
mMsg->Body().FromString( content );
|
|
mMsg->Body().Parse();
|
|
}
|
|
mNeedsAssembly = true;
|
|
if (! partSpecifier.endsWith(".HEADER") )
|
|
{
|
|
// notify observers
|
|
notify();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMessage::updateAttachmentState( DwBodyPart* part )
|
|
{
|
|
if ( !part )
|
|
part = getFirstDwBodyPart();
|
|
|
|
if ( !part )
|
|
{
|
|
// kdDebug(5006) << "updateAttachmentState - no part!" << endl;
|
|
setStatus( KMMsgStatusHasNoAttach );
|
|
return;
|
|
}
|
|
|
|
bool filenameEmpty = true;
|
|
if ( part->hasHeaders() ) {
|
|
if ( part->Headers().HasContentDisposition() ) {
|
|
DwDispositionType cd = part->Headers().ContentDisposition();
|
|
filenameEmpty = cd.Filename().empty();
|
|
if ( filenameEmpty ) {
|
|
// let's try if it is rfc 2231 encoded which mimelib can't handle
|
|
filenameEmpty = KMMsgBase::decodeRFC2231String( KMMsgBase::extractRFC2231HeaderField( cd.AsString().c_str(), "filename" ) ).isEmpty();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( part->hasHeaders() &&
|
|
( ( part->Headers().HasContentDisposition() &&
|
|
!part->Headers().ContentDisposition().Filename().empty() ) ||
|
|
( part->Headers().HasContentType() &&
|
|
!filenameEmpty ) ) )
|
|
{
|
|
// now blacklist certain ContentTypes
|
|
if ( !part->Headers().HasContentType() ||
|
|
( part->Headers().HasContentType() &&
|
|
part->Headers().ContentType().Subtype() != DwMime::kSubtypePgpSignature &&
|
|
part->Headers().ContentType().Subtype() != DwMime::kSubtypePkcs7Signature ) )
|
|
{
|
|
setStatus( KMMsgStatusHasAttach );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// multipart
|
|
if ( part->hasHeaders() &&
|
|
part->Headers().HasContentType() &&
|
|
part->Body().FirstBodyPart() &&
|
|
(DwMime::kTypeMultipart == part->Headers().ContentType().Type() ) )
|
|
{
|
|
updateAttachmentState( part->Body().FirstBodyPart() );
|
|
}
|
|
|
|
// encapsulated message
|
|
if ( part->Body().Message() &&
|
|
part->Body().Message()->Body().FirstBodyPart() )
|
|
{
|
|
updateAttachmentState( part->Body().Message()->Body().FirstBodyPart() );
|
|
}
|
|
|
|
// next part
|
|
if ( part->Next() )
|
|
updateAttachmentState( part->Next() );
|
|
else if ( attachmentState() == KMMsgAttachmentUnknown )
|
|
setStatus( KMMsgStatusHasNoAttach );
|
|
}
|
|
|
|
void KMMessage::setBodyFromUnicode( const QString & str ) {
|
|
QCString encoding = KMMsgBase::autoDetectCharset( charset(), KMMessage::preferredCharsets(), str );
|
|
if ( encoding.isEmpty() )
|
|
encoding = "utf-8";
|
|
const QTextCodec * codec = KMMsgBase::codecForName( encoding );
|
|
assert( codec );
|
|
QValueList<int> dummy;
|
|
setCharset( encoding );
|
|
setBodyAndGuessCte( codec->fromUnicode( str ), dummy, false /* no 8bit */ );
|
|
}
|
|
|
|
const QTextCodec * KMMessage::codec() const {
|
|
const QTextCodec * c = mOverrideCodec;
|
|
if ( !c )
|
|
// no override-codec set for this message, try the CT charset parameter:
|
|
c = KMMsgBase::codecForName( charset() );
|
|
if ( !c ) {
|
|
// Ok, no override and nothing in the message, let's use the fallback
|
|
// the user configured
|
|
c = KMMsgBase::codecForName( GlobalSettings::self()->fallbackCharacterEncoding().latin1() );
|
|
}
|
|
if ( !c )
|
|
// no charset means us-ascii (RFC 2045), so using local encoding should
|
|
// be okay
|
|
c = kmkernel->networkCodec();
|
|
assert( c );
|
|
return c;
|
|
}
|
|
|
|
QString KMMessage::bodyToUnicode(const QTextCodec* codec) const {
|
|
if ( !codec )
|
|
// No codec was given, so try the charset in the mail
|
|
codec = this->codec();
|
|
assert( codec );
|
|
|
|
return codec->toUnicode( bodyDecoded() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
QCString KMMessage::mboxMessageSeparator()
|
|
{
|
|
QCString str( KPIM::getFirstEmailAddress( rawHeaderField("From") ) );
|
|
if ( str.isEmpty() )
|
|
str = "unknown@unknown.invalid";
|
|
QCString dateStr( dateShortStr() );
|
|
if ( dateStr.isEmpty() ) {
|
|
time_t t = ::time( 0 );
|
|
dateStr = ctime( &t );
|
|
const int len = dateStr.length();
|
|
if ( dateStr[len-1] == '\n' )
|
|
dateStr.truncate( len - 1 );
|
|
}
|
|
return "From " + str + " " + dateStr + "\n";
|
|
}
|
|
|
|
void KMMessage::deleteWhenUnused()
|
|
{
|
|
sPendingDeletes << this;
|
|
}
|