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.
1266 lines
37 KiB
1266 lines
37 KiB
// kmmsgbase.cpp |
|
|
|
#include <config-kmail.h> |
|
|
|
#include "kmmsgbase.h" |
|
#include "globalsettings.h" |
|
|
|
#include "kmfolderindex.h" |
|
#include "kmfolder.h" |
|
#include "kmheaders.h" |
|
#include "kmmsgdict.h" |
|
#include "kmmessagetag.h" |
|
#include "messageproperty.h" |
|
#include <QByteArray> |
|
using KMail::MessageProperty; |
|
|
|
#include <kdebug.h> |
|
#include <kde_file.h> |
|
#include <kglobal.h> |
|
#include <kcharsets.h> |
|
#include <kascii.h> |
|
#include <kcodecs.h> |
|
#include <kdatetime.h> |
|
#include <kconfiggroup.h> |
|
|
|
#include <mimelib/mimepp.h> |
|
#include <kmime/kmime_codecs.h> |
|
|
|
#include <QTextCodec> |
|
#include <q3deepcopy.h> |
|
#include <QRegExp> |
|
|
|
#include <ctype.h> |
|
#include <stdlib.h> |
|
#include <unistd.h> |
|
|
|
#ifdef HAVE_BYTESWAP_H |
|
#include <byteswap.h> |
|
#endif |
|
|
|
// We define functions as kmail_swap_NN so that we don't get compile errors |
|
// on platforms where bswap_NN happens to be a function instead of a define. |
|
|
|
/* Swap bytes in 16 bit value. */ |
|
#ifdef bswap_16 |
|
#define kmail_swap_16(x) bswap_16(x) |
|
#else |
|
#define kmail_swap_16(x) \ |
|
((((x) >> 8) & 0xff) | (((x) & 0xff) << 8)) |
|
#endif |
|
|
|
/* Swap bytes in 32 bit value. */ |
|
#ifdef bswap_32 |
|
#define kmail_swap_32(x) bswap_32(x) |
|
#else |
|
#define kmail_swap_32(x) \ |
|
((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \ |
|
(((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) |
|
#endif |
|
|
|
/* Swap bytes in 64 bit value. */ |
|
#ifdef bswap_64 |
|
#define kmail_swap_64(x) bswap_64(x) |
|
#else |
|
#define kmail_swap_64(x) \ |
|
((((x) & 0xff00000000000000ull) >> 56) \ |
|
| (((x) & 0x00ff000000000000ull) >> 40) \ |
|
| (((x) & 0x0000ff0000000000ull) >> 24) \ |
|
| (((x) & 0x000000ff00000000ull) >> 8) \ |
|
| (((x) & 0x00000000ff000000ull) << 8) \ |
|
| (((x) & 0x0000000000ff0000ull) << 24) \ |
|
| (((x) & 0x000000000000ff00ull) << 40) \ |
|
| (((x) & 0x00000000000000ffull) << 56)) |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
KMMsgBase::KMMsgBase(KMFolder* aParentFolder) |
|
: mParent( aParentFolder ), mIndexOffset( 0 ), |
|
mIndexLength( 0 ), mDirty( false ), mEnableUndo( false ), |
|
mStatus(), mTagList( 0 ) |
|
{ |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
KMMsgBase::~KMMsgBase() |
|
{ |
|
MessageProperty::forget( this ); |
|
delete mTagList; |
|
} |
|
|
|
KMFolderIndex* KMMsgBase::storage() const |
|
{ |
|
// TODO: How did this ever work? What about KMFolderSearch that does |
|
// not inherit KMFolderIndex? |
|
if( mParent ) |
|
return static_cast<KMFolderIndex*>( mParent->storage() ); |
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMsgBase::assign(const KMMsgBase* other) |
|
{ |
|
mParent = other->mParent; |
|
mDirty = other->mDirty; |
|
mIndexOffset = other->mIndexOffset; |
|
mIndexLength = other->mIndexLength; |
|
//Not sure why these 4 are copied here, but mStatus is not. Nevertheless, |
|
//it probably does no harm to copy the taglist here |
|
if ( other->tagList() ) { |
|
if ( !mTagList ) |
|
mTagList = new KMMessageTagList(); |
|
*mTagList = *other->tagList(); |
|
} else { |
|
delete mTagList; |
|
mTagList = 0; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
KMMsgBase& KMMsgBase::operator=(const KMMsgBase& other) |
|
{ |
|
assign(&other); |
|
return *this; |
|
} |
|
|
|
|
|
//---------------------------------------------------------------------------- |
|
KMMsgBase::KMMsgBase( const KMMsgBase& other ) : mTagList( 0 ) |
|
{ |
|
assign( &other ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
bool KMMsgBase::isMessage(void) const |
|
{ |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMsgBase::toggleStatus(const MessageStatus& aStatus, int idx) |
|
{ |
|
mDirty = true; |
|
MessageStatus oldStatus = mStatus; |
|
mStatus.toggle( aStatus ); |
|
if (storage()) { |
|
if (idx < 0) |
|
idx = storage()->find( this ); |
|
storage()->msgStatusChanged( oldStatus, mStatus, idx ); |
|
storage()->headerOfMsgChanged(this, idx); |
|
} |
|
|
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMsgBase::setStatus(const MessageStatus& aStatus, int idx) |
|
{ |
|
MessageStatus oldStatus = mStatus; |
|
mStatus.set( aStatus ); |
|
if ( oldStatus != mStatus ) { |
|
mDirty = true; |
|
if ( storage() ) { |
|
if (idx < 0) |
|
idx = storage()->find( this ); |
|
storage()->msgStatusChanged( oldStatus, mStatus, idx ); |
|
storage()->headerOfMsgChanged( this, idx ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMsgBase::setStatus(const char* aStatusStr, const char* aXStatusStr) |
|
{ |
|
// cumulate the changes in "status" to avoid multiple emits of |
|
// msgStatusChanged(...) when calling setStatus() multiple times |
|
MessageStatus status; |
|
|
|
// first try to find status from "X-Status" field if given |
|
if (aXStatusStr) { |
|
status.setStatusFromStr( aXStatusStr ); |
|
} |
|
|
|
// Merge the contents of the "Status" field |
|
if (aStatusStr) { |
|
if ((aStatusStr[0]== 'R' && aStatusStr[1]== 'O') || |
|
(aStatusStr[0]== 'O' && aStatusStr[1]== 'R')) { |
|
status.setOld(); |
|
status.setRead(); |
|
} |
|
else if (aStatusStr[0] == 'R') |
|
status.setRead(); |
|
else if (aStatusStr[0] == 'D') |
|
status.setDeleted(); |
|
else |
|
status.setNew(); |
|
} |
|
setStatus( status ); |
|
} |
|
|
|
|
|
void KMMsgBase::setEncryptionState( const KMMsgEncryptionState /*status*/, int idx ) |
|
{ |
|
//kDebug(5006) <<"***setEncryptionState1(" << status <<" )"; |
|
mDirty = true; |
|
if (storage()) |
|
storage()->headerOfMsgChanged(this, idx); |
|
} |
|
|
|
void KMMsgBase::setEncryptionStateChar( QChar status, int idx ) |
|
{ |
|
//kDebug(5006) <<"***setEncryptionState2(" << (status.isNull() ? '?' : status.toLatin1()) <<" )"; |
|
|
|
if( status.toLatin1() == (char)KMMsgEncryptionStateUnknown ) |
|
setEncryptionState( KMMsgEncryptionStateUnknown, idx ); |
|
else if( status.toLatin1() == (char)KMMsgNotEncrypted ) |
|
setEncryptionState( KMMsgNotEncrypted, idx ); |
|
else if( status.toLatin1() == (char)KMMsgPartiallyEncrypted ) |
|
setEncryptionState( KMMsgPartiallyEncrypted, idx ); |
|
else if( status.toLatin1() == (char)KMMsgFullyEncrypted ) |
|
setEncryptionState( KMMsgFullyEncrypted, idx ); |
|
else |
|
setEncryptionState( KMMsgEncryptionStateUnknown, idx ); |
|
} |
|
|
|
|
|
void KMMsgBase::setSignatureState( const KMMsgSignatureState /*status*/, int idx ) |
|
{ |
|
//kDebug(5006) <<"***setSignatureState1(" << status <<" )"; |
|
mDirty = true; |
|
if (storage()) |
|
storage()->headerOfMsgChanged(this, idx); |
|
} |
|
|
|
void KMMsgBase::setMDNSentState( KMMsgMDNSentState, int idx ) { |
|
mDirty = true; |
|
if ( storage() ) |
|
storage()->headerOfMsgChanged(this, idx); |
|
} |
|
|
|
void KMMsgBase::setSignatureStateChar( QChar status, int idx ) |
|
{ |
|
//kDebug(5006) <<"***setSignatureState2(" << (status.isNull() ? '?' : status.toLatin1()) <<" )"; |
|
|
|
if( status.toLatin1() == (char)KMMsgSignatureStateUnknown ) |
|
setSignatureState( KMMsgSignatureStateUnknown, idx ); |
|
else if( status.toLatin1() == (char)KMMsgNotSigned ) |
|
setSignatureState( KMMsgNotSigned, idx ); |
|
else if( status.toLatin1() == (char)KMMsgPartiallySigned ) |
|
setSignatureState( KMMsgPartiallySigned,idx ); |
|
else if( status.toLatin1() == (char)KMMsgFullySigned ) |
|
setSignatureState( KMMsgFullySigned, idx ); |
|
else |
|
setSignatureState( KMMsgSignatureStateUnknown, idx ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
MessageStatus& KMMsgBase::status() |
|
{ |
|
return mStatus; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
const MessageStatus& KMMsgBase::messageStatus() const |
|
{ |
|
return mStatus; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMsgBase::setDate(const QByteArray& aDateStr) |
|
{ |
|
setDate( KDateTime::fromString( aDateStr, KDateTime::RFCDate ).toTime_t() ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMsgBase::dateStr(void) const |
|
{ |
|
time_t d = date(); |
|
return KMime::DateFormatter::formatDate(KMime::DateFormatter::Fancy, d); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMsgBase::skipKeyword(const QString& aStr, QChar sepChar, |
|
bool* hasKeyword) |
|
{ |
|
unsigned int i = 0, maxChars = 3; |
|
QString str = aStr; |
|
|
|
while (str[0] == ' ') str.remove(0,1); |
|
if (hasKeyword) *hasKeyword=false; |
|
|
|
unsigned int strLength(str.length()); |
|
for (i=0; i < strLength && i < maxChars; i++) |
|
{ |
|
if (str[i] < 'A' || str[i] == sepChar) break; |
|
} |
|
|
|
if (str[i] == sepChar) // skip following spaces too |
|
{ |
|
do { |
|
i++; |
|
} while (str[i] == ' '); |
|
if (hasKeyword) *hasKeyword=true; |
|
return str.mid(i); |
|
} |
|
return str; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
const QTextCodec* KMMsgBase::codecForName(const QByteArray& _str) |
|
{ |
|
if (_str.isEmpty()) |
|
return 0; |
|
QByteArray codec = _str; |
|
kAsciiToLower(codec.data()); |
|
return KGlobal::charsets()->codecForName(codec); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QByteArray KMMsgBase::toUsAscii(const QString& _str, bool *ok) |
|
{ |
|
bool all_ok =true; |
|
QString result = _str; |
|
int len = result.length(); |
|
for (int i = 0; i < len; i++) |
|
if (result.at(i).unicode() >= 128) { |
|
result[i] = '?'; |
|
all_ok = false; |
|
} |
|
if (ok) |
|
*ok = all_ok; |
|
return result.toLatin1(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QStringList KMMsgBase::supportedEncodings(bool usAscii) |
|
{ |
|
QStringList encodingNames = KGlobal::charsets()->availableEncodingNames(); |
|
QStringList encodings; |
|
QMap<QString,bool> mimeNames; |
|
for (QStringList::Iterator it = encodingNames.begin(); |
|
it != encodingNames.end(); it++) |
|
{ |
|
QTextCodec *codec = KGlobal::charsets()->codecForName(*it); |
|
QString mimeName = (codec) ? QString(codec->name()).toLower() : (*it); |
|
if (!mimeNames.contains(mimeName) ) |
|
{ |
|
encodings.append( KGlobal::charsets()->descriptionForEncoding(*it) ); |
|
mimeNames.insert( mimeName, true ); |
|
} |
|
} |
|
encodings.sort(); |
|
if (usAscii) |
|
encodings.prepend(KGlobal::charsets()->descriptionForEncoding("us-ascii") ); |
|
return encodings; |
|
} |
|
|
|
namespace { |
|
// don't rely on isblank(), which is a GNU extension in |
|
// <cctype>. But if someone wants to write a configure test for |
|
// isblank(), we can then rename this function to isblank and #ifdef |
|
// it's definition... |
|
inline bool isBlank( char ch ) { return ch == ' ' || ch == '\t' ; } |
|
|
|
QByteArray unfold( const QByteArray & header ) { |
|
if ( header.isEmpty() ) |
|
return QByteArray(); |
|
|
|
QByteArray result( header.size(), '\0' ); // size() >= length()+1 and size() is O(1) |
|
char * d = result.data(); |
|
|
|
for ( const char * s = header.data() ; *s ; ) |
|
if ( *s == '\r' ) { // ignore |
|
++s; |
|
continue; |
|
} else if ( *s == '\n' ) { // unfold |
|
while ( isBlank( *++s ) ); |
|
*d++ = ' '; |
|
} else |
|
*d++ = *s++; |
|
|
|
*d++ = '\0'; |
|
result.truncate( d - result.data() ); |
|
return result; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMsgBase::decodeRFC2047String( const QByteArray &aStr, |
|
const QByteArray &prefCharset ) |
|
{ |
|
if ( aStr.isEmpty() ) { |
|
return QString(); |
|
} |
|
|
|
const QByteArray str = unfold( aStr ); |
|
|
|
if ( str.isEmpty() ) { |
|
return QString(); |
|
} |
|
|
|
if ( str.indexOf( "=?" ) < 0 ) { |
|
QByteArray charsetName; |
|
if ( !prefCharset.isEmpty() ) { |
|
if ( kasciistricmp( prefCharset.data(), "us-ascii" ) == 0 ) { |
|
charsetName = "utf-8"; |
|
} else { |
|
charsetName = prefCharset; |
|
} |
|
} else { |
|
charsetName = GlobalSettings::self()->fallbackCharacterEncoding().toLatin1(); |
|
} |
|
const QTextCodec *codec = KMMsgBase::codecForName( charsetName ); |
|
if ( ! codec ) { |
|
codec = kmkernel->networkCodec(); |
|
} |
|
return codec->toUnicode( str ); |
|
} |
|
|
|
QString result; |
|
QByteArray LWSP_buffer; |
|
bool lastWasEncodedWord = false; |
|
|
|
for ( const char * pos = str.data() ; *pos ; ++pos ) { |
|
// collect LWSP after encoded-words, |
|
// because we might need to throw it out |
|
// (when the next word is an encoded-word) |
|
if ( lastWasEncodedWord && isBlank( pos[0] ) ) { |
|
LWSP_buffer += pos[0]; |
|
continue; |
|
} |
|
// verbatimly copy normal text |
|
if (pos[0]!='=' || pos[1]!='?') { |
|
result += LWSP_buffer + pos[0]; |
|
LWSP_buffer = 0; |
|
lastWasEncodedWord = false; |
|
continue; |
|
} |
|
// found possible encoded-word |
|
const char * const beg = pos; |
|
{ |
|
// parse charset name |
|
QByteArray charset; |
|
int i = 2; |
|
pos += 2; |
|
for ( ; *pos != '?' && ( *pos==' ' || ispunct(*pos) || isalnum(*pos) ); |
|
++i, ++pos ) { |
|
charset += *pos; |
|
} |
|
if ( *pos!='?' || i<4 ) |
|
goto invalid_encoded_word; |
|
|
|
// get encoding and check delimiting question marks |
|
const char encoding[2] = { pos[1], '\0' }; |
|
if (pos[2]!='?' || (encoding[0]!='Q' && encoding[0]!='q' && |
|
encoding[0]!='B' && encoding[0]!='b')) |
|
goto invalid_encoded_word; |
|
pos+=3; i+=3; // skip ?x? |
|
const char * enc_start = pos; |
|
// search for end of encoded part |
|
while ( *pos && !(*pos=='?' && *(pos+1)=='=') ) { |
|
i++; |
|
pos++; |
|
} |
|
if ( !*pos ) |
|
goto invalid_encoded_word; |
|
|
|
// valid encoding: decode and throw away separating LWSP |
|
const KMime::Codec * c = KMime::Codec::codecForName( encoding ); |
|
kFatal( !c, 5006 ) <<"No \"" << encoding <<"\" codec!?"; |
|
|
|
QByteArray in = QByteArray::fromRawData( enc_start, pos - enc_start ); |
|
const QByteArray enc = c->decode( in ); |
|
in.clear(); |
|
|
|
const QTextCodec * codec = codecForName(charset); |
|
if (!codec) codec = kmkernel->networkCodec(); |
|
result += codec->toUnicode(enc); |
|
lastWasEncodedWord = true; |
|
|
|
++pos; // eat '?' (for loop eats '=') |
|
LWSP_buffer = 0; |
|
} |
|
continue; |
|
invalid_encoded_word: |
|
// invalid encoding, keep separating LWSP. |
|
pos = beg; |
|
if ( !LWSP_buffer.isNull() ) |
|
result += LWSP_buffer; |
|
result += "=?"; |
|
lastWasEncodedWord = false; |
|
++pos; // eat '?' (for loop eats '=') |
|
LWSP_buffer = 0; |
|
} |
|
return result; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
static const QByteArray especials = "()<>@,;:\"/[]?.= \033"; |
|
|
|
QByteArray KMMsgBase::encodeRFC2047Quoted( const QByteArray & s, bool base64 ) { |
|
const char * codecName = base64 ? "b" : "q" ; |
|
const KMime::Codec * codec = KMime::Codec::codecForName( codecName ); |
|
kFatal( !codec, 5006 ) <<"No \"" << codecName <<"\" found!?"; |
|
QByteArray in = QByteArray::fromRawData( s.data(), s.length() ); |
|
const QByteArray result = codec->encode( in ); |
|
in.clear(); |
|
return QByteArray( result.data(), result.size()); |
|
} |
|
|
|
QByteArray KMMsgBase::encodeRFC2047String(const QString& _str, |
|
const QByteArray& charset) |
|
{ |
|
static const QString dontQuote = "\"()<>,@"; |
|
|
|
if (_str.isEmpty()) return QByteArray(); |
|
if (charset == "us-ascii") return toUsAscii(_str); |
|
|
|
QByteArray cset; |
|
if (charset.isEmpty()) |
|
{ |
|
cset = kmkernel->networkCodec()->name(); |
|
kAsciiToLower(cset.data()); |
|
} |
|
else cset = charset; |
|
|
|
const QTextCodec *codec = codecForName(cset); |
|
if (!codec) codec = kmkernel->networkCodec(); |
|
|
|
unsigned int nonAscii = 0; |
|
unsigned int strLength(_str.length()); |
|
for (unsigned int i = 0; i < strLength; i++) |
|
if (_str.at(i).unicode() >= 128) nonAscii++; |
|
bool useBase64 = (nonAscii * 6 > strLength); |
|
|
|
unsigned int start, stop, p, pos = 0, encLength; |
|
QByteArray result; |
|
bool breakLine = false; |
|
const unsigned int maxLen = 75 - 7 - cset.length(); |
|
|
|
while (pos < strLength) |
|
{ |
|
start = pos; p = pos; |
|
while (p < strLength) |
|
{ |
|
if (!breakLine && (_str.at(p) == ' ' || dontQuote.contains(_str.at(p)) )) |
|
start = p + 1; |
|
if (_str.at(p).unicode() >= 128 || _str.at(p).unicode() < 32) |
|
break; |
|
p++; |
|
} |
|
if (breakLine || p < strLength) |
|
{ |
|
while (dontQuote.contains(_str.at(start)) ) start++; |
|
stop = start; |
|
while (stop < strLength && !dontQuote.contains(_str.at(stop)) ) |
|
stop++; |
|
result += _str.mid(pos, start - pos).toLatin1(); |
|
encLength = encodeRFC2047Quoted(codec->fromUnicode(_str. |
|
mid(start, stop - start)), useBase64).length(); |
|
breakLine = (encLength > maxLen); |
|
if (breakLine) |
|
{ |
|
int dif = (stop - start) / 2; |
|
int step = dif; |
|
while (abs(step) > 1) |
|
{ |
|
encLength = encodeRFC2047Quoted(codec->fromUnicode(_str. |
|
mid(start, dif)), useBase64).length(); |
|
step = (encLength > maxLen) ? (-abs(step) / 2) : (abs(step) / 2); |
|
dif += step; |
|
} |
|
stop = start + dif; |
|
} |
|
p = (stop >= strLength ? strLength - 1 : stop); |
|
while (p > start && _str.at(p) != ' ') p--; |
|
if (p > start) stop = p; |
|
if (result.right(3) == "?= ") start--; |
|
if (result.right(5) == "?=\n ") { |
|
start--; result.truncate(result.length() - 1); |
|
} |
|
int lastNewLine = result.lastIndexOf("\n "); |
|
if (!result.mid(lastNewLine).trimmed().isEmpty() |
|
&& result.length() - lastNewLine + encLength + 2 > maxLen) |
|
result += "\n "; |
|
result += "=?"; |
|
result += cset; |
|
result += (useBase64) ? "?b?" : "?q?"; |
|
result += encodeRFC2047Quoted(codec->fromUnicode(_str.mid(start, |
|
stop - start)), useBase64); |
|
result += "?="; |
|
if (breakLine) result += "\n "; |
|
pos = stop; |
|
} else { |
|
result += _str.mid(pos).toLatin1(); |
|
break; |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QByteArray KMMsgBase::encodeRFC2231String( const QString& _str, |
|
const QByteArray& charset ) |
|
{ |
|
if ( _str.isEmpty() ) |
|
return QByteArray(); |
|
|
|
QByteArray cset; |
|
if ( charset.isEmpty() ) |
|
{ |
|
cset = kmkernel->networkCodec()->name(); |
|
kAsciiToLower( cset.data() ); |
|
} |
|
else |
|
cset = charset; |
|
const QTextCodec *codec = codecForName( cset ); |
|
QByteArray latin; |
|
if ( charset == "us-ascii" ) |
|
latin = toUsAscii( _str ); |
|
else if ( codec ) |
|
latin = codec->fromUnicode( _str ); |
|
else |
|
latin = _str.toLocal8Bit(); |
|
|
|
char *l; |
|
for ( l = latin.data(); *l; ++l ) { |
|
if ( ( ( *l & 0xE0 ) == 0 ) || ( *l & 0x80 ) ) |
|
// *l is control character or 8-bit char |
|
break; |
|
} |
|
if ( !*l ) |
|
return latin; |
|
|
|
QByteArray result = cset + "''"; |
|
for ( l = latin.data(); *l; ++l ) { |
|
bool needsQuoting = ( *l & 0x80 ); |
|
if( !needsQuoting ) { |
|
int len = especials.length(); |
|
for ( int i = 0; i < len; i++ ) |
|
if ( *l == especials[i] ) { |
|
needsQuoting = true; |
|
break; |
|
} |
|
} |
|
if ( needsQuoting ) { |
|
result += '%'; |
|
unsigned char hexcode; |
|
hexcode = ( ( *l & 0xF0 ) >> 4 ) + 48; |
|
if ( hexcode >= 58 ) |
|
hexcode += 7; |
|
result += hexcode; |
|
hexcode = ( *l & 0x0F ) + 48; |
|
if ( hexcode >= 58 ) |
|
hexcode += 7; |
|
result += hexcode; |
|
} else { |
|
result += *l; |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMsgBase::decodeRFC2231String(const QByteArray& _str) |
|
{ |
|
int p = _str.indexOf('\''); |
|
if (p < 0) return kmkernel->networkCodec()->toUnicode(_str); |
|
|
|
QByteArray charset = _str.left(p); |
|
|
|
QByteArray st = _str.mid(_str.lastIndexOf('\'') + 1); |
|
char ch, ch2; |
|
p = 0; |
|
while (p < (int)st.length()) |
|
{ |
|
if (st.at(p) == 37) |
|
{ |
|
ch = st.at(p+1) - 48; |
|
if (ch > 16) ch -= 7; |
|
ch2 = st.at(p+2) - 48; |
|
if (ch2 > 16) ch2 -= 7; |
|
st[p] = ch * 16 + ch2; |
|
st.remove( p+1, 2 ); |
|
} |
|
p++; |
|
} |
|
QString result; |
|
const QTextCodec * codec = codecForName( charset ); |
|
if ( !codec ) |
|
codec = kmkernel->networkCodec(); |
|
return codec->toUnicode( st ); |
|
} |
|
|
|
QByteArray KMMsgBase::extractRFC2231HeaderField( const QByteArray &aStr, |
|
const QByteArray &field ) |
|
{ |
|
int n = -1; |
|
QByteArray str; |
|
bool found = false; |
|
while ( n <= 0 || found ) { |
|
QString pattern( field ); |
|
// match a literal * after the fieldname, as defined by RFC2231 |
|
pattern += "[*]"; |
|
if ( n >= 0 ) { |
|
// If n<0, check for fieldname*=..., otherwise for fieldname*n= |
|
pattern += QString::number( n ) + "[*]?"; |
|
} |
|
pattern += '='; |
|
|
|
QRegExp fnamePart( pattern, Qt::CaseInsensitive ); |
|
int startPart = fnamePart.indexIn( aStr ); |
|
int endPart; |
|
found = ( startPart >= 0 ); |
|
if ( found ) { |
|
startPart += fnamePart.matchedLength(); |
|
// Quoted values end at the ending quote |
|
if ( aStr[startPart] == '"' ) { |
|
startPart++; // the double quote isn't part of the filename |
|
endPart = aStr.indexOf( '"', startPart ) - 1; |
|
} else { |
|
endPart = aStr.indexOf( ';', startPart ) - 1; |
|
} |
|
if ( endPart < 0 ) { |
|
endPart = 32767; |
|
} |
|
str += aStr.mid( startPart, endPart - startPart + 1 ).trimmed(); |
|
} |
|
n++; |
|
} |
|
return str; |
|
} |
|
|
|
QString KMMsgBase::base64EncodedMD5( const QString & s, bool utf8 ) { |
|
if (s.trimmed().isEmpty()) return ""; |
|
if ( utf8 ) |
|
return base64EncodedMD5( s.trimmed().toUtf8() ); // QCString overload |
|
else |
|
return base64EncodedMD5( s.trimmed().toLatin1() ); // const char * overload |
|
} |
|
|
|
QString KMMsgBase::base64EncodedMD5( const QByteArray & s ) { |
|
if (s.trimmed().isEmpty()) return ""; |
|
return base64EncodedMD5( s.trimmed().data() ); |
|
} |
|
|
|
QString KMMsgBase::base64EncodedMD5( const char * s, int len ) { |
|
if (!s || !len) return ""; |
|
static const int Base64EncodedMD5Len = 22; |
|
KMD5 md5( s, len ); |
|
return md5.base64Digest().left( Base64EncodedMD5Len ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QByteArray KMMsgBase::autoDetectCharset(const QByteArray &_encoding, const QStringList &encodingList, const QString &text) |
|
{ |
|
QStringList charsets = encodingList; |
|
if (!_encoding.isEmpty()) |
|
{ |
|
QString currentCharset = QString::fromLatin1(_encoding); |
|
charsets.removeAll(currentCharset); |
|
charsets.prepend(currentCharset); |
|
} |
|
|
|
QStringList::ConstIterator it = charsets.begin(); |
|
for (; it != charsets.end(); ++it) |
|
{ |
|
QByteArray encoding = (*it).toLatin1(); |
|
if (encoding == "locale") |
|
{ |
|
encoding = kmkernel->networkCodec()->name(); |
|
kAsciiToLower(encoding.data()); |
|
} |
|
if (text.isEmpty()) |
|
return encoding; |
|
if (encoding == "us-ascii") { |
|
bool ok; |
|
(void) KMMsgBase::toUsAscii(text, &ok); |
|
if (ok) |
|
return encoding; |
|
} |
|
else |
|
{ |
|
const QTextCodec *codec = KMMsgBase::codecForName(encoding); |
|
if (!codec) { |
|
kDebug(5006) <<"Auto-Charset: Something is wrong and I can not get a codec. [" << encoding <<"]"; |
|
} else { |
|
if (codec->canEncode(text)) |
|
return encoding; |
|
} |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
unsigned long KMMsgBase::getMsgSerNum() const |
|
{ |
|
unsigned long msn = MessageProperty::serialCache( this ); |
|
if (msn) |
|
return msn; |
|
if (mParent) { |
|
int index = mParent->find((KMMsgBase*)this); |
|
msn = KMMsgDict::instance()->getMsgSerNum(mParent, index); |
|
if (msn) |
|
MessageProperty::setSerialCache( this, msn ); |
|
} |
|
return msn; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
KMMsgAttachmentState KMMsgBase::attachmentState() const |
|
{ |
|
if ( mStatus.hasAttachment() ) |
|
return KMMsgHasAttachment; |
|
// FIXME |
|
// else if (st & KMMsgStatusHasNoAttach) |
|
// return KMMsgHasNoAttachment; |
|
else |
|
return KMMsgAttachmentUnknown; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
static void swapEndian(QString &str) |
|
{ |
|
uint len = str.length(); |
|
str = Q3DeepCopy<QString>(str); |
|
QChar *unicode = const_cast<QChar*>( str.unicode() ); |
|
for (uint i = 0; i < len; i++) |
|
unicode[i] = kmail_swap_16(unicode[i].unicode()); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
static int g_chunk_length = 0, g_chunk_offset=0; |
|
static uchar *g_chunk = 0; |
|
|
|
namespace { |
|
template < typename T > void copy_from_stream( T & x ) { |
|
if( g_chunk_offset + int(sizeof(T)) > g_chunk_length ) { |
|
g_chunk_offset = g_chunk_length; |
|
kDebug( 5006 ) <<"This should never happen.."; |
|
x = 0; |
|
} else { |
|
// the memcpy is optimized out by the compiler for the values |
|
// of sizeof(T) that is called with |
|
memcpy( &x, g_chunk + g_chunk_offset, sizeof(T) ); |
|
g_chunk_offset += sizeof(T); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMsgBase::getStringPart(MsgPartType t) const |
|
{ |
|
retry: |
|
QString ret; |
|
|
|
g_chunk_offset = 0; |
|
bool using_mmap = false; |
|
bool swapByteOrder = storage()->indexSwapByteOrder(); |
|
if (storage()->indexStreamBasePtr()) { |
|
if (g_chunk) |
|
free(g_chunk); |
|
using_mmap = true; |
|
if ( mIndexOffset > (off_t)storage()->indexStreamLength() ) { |
|
// This message has not been indexed yet, data would lie |
|
// outside the index data structures so do not touch it. |
|
return QString(); |
|
} |
|
g_chunk = storage()->indexStreamBasePtr() + mIndexOffset; |
|
g_chunk_length = mIndexLength; |
|
} else { |
|
if(!storage()->mIndexStream) |
|
return ret; |
|
if (g_chunk_length < mIndexLength) |
|
g_chunk = (uchar *)realloc(g_chunk, g_chunk_length = mIndexLength); |
|
off_t first_off = KDE_ftell(storage()->mIndexStream); |
|
KDE_fseek(storage()->mIndexStream, mIndexOffset, SEEK_SET); |
|
fread( g_chunk, mIndexLength, 1, storage()->mIndexStream); |
|
KDE_fseek(storage()->mIndexStream, first_off, SEEK_SET); |
|
} |
|
|
|
MsgPartType type; |
|
quint16 l; |
|
while(g_chunk_offset < mIndexLength) { |
|
quint32 tmp; |
|
copy_from_stream(tmp); |
|
copy_from_stream(l); |
|
if (swapByteOrder) |
|
{ |
|
tmp = kmail_swap_32(tmp); |
|
l = kmail_swap_16(l); |
|
} |
|
type = (MsgPartType) tmp; |
|
if(g_chunk_offset + l > mIndexLength) { |
|
kDebug(5006) <<"This should never happen.."; |
|
if(using_mmap) { |
|
g_chunk_length = 0; |
|
g_chunk = 0; |
|
} |
|
if (!storage()->recreateIndex()) |
|
return QString(); |
|
goto retry; |
|
} |
|
if(type == t) { |
|
// This works because the QString constructor does a memcpy. |
|
// Otherwise we would need to be concerned about the alignment. |
|
if(l) |
|
ret = QString((QChar *)(g_chunk + g_chunk_offset), l/2); |
|
break; |
|
} |
|
g_chunk_offset += l; |
|
} |
|
if(using_mmap) { |
|
g_chunk_length = 0; |
|
g_chunk = 0; |
|
} |
|
// Normally we need to swap the byte order because the QStrings are written |
|
// in the style of Qt2 (MSB -> network ordered). |
|
// QStrings in Qt3 expect host ordering. |
|
// On e.g. Intel host ordering is LSB, on e.g. Sparc it is MSB. |
|
|
|
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN |
|
// Byte order is little endian (swap is true) |
|
swapEndian(ret); |
|
#else |
|
// Byte order is big endian (swap is false) |
|
#endif |
|
|
|
return ret; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
off_t KMMsgBase::getLongPart(MsgPartType t) const |
|
{ |
|
retry: |
|
off_t ret = 0; |
|
|
|
g_chunk_offset = 0; |
|
bool using_mmap = false; |
|
int sizeOfLong = storage()->indexSizeOfLong(); |
|
bool swapByteOrder = storage()->indexSwapByteOrder(); |
|
if (storage()->indexStreamBasePtr()) { |
|
if (g_chunk) |
|
free(g_chunk); |
|
using_mmap = true; |
|
g_chunk = storage()->indexStreamBasePtr() + mIndexOffset; |
|
g_chunk_length = mIndexLength; |
|
} else { |
|
if (!storage()->mIndexStream) |
|
return ret; |
|
assert(mIndexLength >= 0); |
|
if (g_chunk_length < mIndexLength) |
|
g_chunk = (uchar *)realloc(g_chunk, g_chunk_length = mIndexLength); |
|
off_t first_off = KDE_ftell(storage()->mIndexStream); |
|
KDE_fseek(storage()->mIndexStream, mIndexOffset, SEEK_SET); |
|
fread( g_chunk, mIndexLength, 1, storage()->mIndexStream); |
|
KDE_fseek(storage()->mIndexStream, first_off, SEEK_SET); |
|
} |
|
|
|
MsgPartType type; |
|
quint16 l; |
|
while (g_chunk_offset < mIndexLength) { |
|
quint32 tmp; |
|
copy_from_stream(tmp); |
|
copy_from_stream(l); |
|
if (swapByteOrder) |
|
{ |
|
tmp = kmail_swap_32(tmp); |
|
l = kmail_swap_16(l); |
|
} |
|
type = (MsgPartType) tmp; |
|
|
|
if (g_chunk_offset + l > mIndexLength) { |
|
kDebug(5006) <<"This should never happen.."; |
|
if(using_mmap) { |
|
g_chunk_length = 0; |
|
g_chunk = 0; |
|
} |
|
if (!storage()->recreateIndex()) |
|
return 0; |
|
goto retry; |
|
} |
|
if(type == t) { |
|
assert(sizeOfLong == l); |
|
if (sizeOfLong == sizeof(ret)) |
|
{ |
|
copy_from_stream(ret); |
|
if (swapByteOrder) |
|
{ |
|
if (sizeof(ret) == 4) |
|
ret = kmail_swap_32(ret); |
|
else |
|
ret = kmail_swap_64(ret); |
|
} |
|
} |
|
else if (sizeOfLong == 4) |
|
{ |
|
// Long is stored as 4 bytes in index file, sizeof(long) = 8 |
|
quint32 ret_32; |
|
copy_from_stream(ret_32); |
|
if (swapByteOrder) |
|
ret_32 = kmail_swap_32(ret_32); |
|
ret = ret_32; |
|
} |
|
else if (sizeOfLong == 8) |
|
{ |
|
// Long is stored as 8 bytes in index file, sizeof(long) = 4 |
|
quint32 ret_1; |
|
quint32 ret_2; |
|
copy_from_stream(ret_1); |
|
copy_from_stream(ret_2); |
|
if (!swapByteOrder) |
|
{ |
|
// Index file order is the same as the order of this CPU. |
|
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN |
|
// Index file order is little endian |
|
ret = ret_1; // We drop the 4 most significant bytes |
|
#else |
|
// Index file order is big endian |
|
ret = ret_2; // We drop the 4 most significant bytes |
|
#endif |
|
} |
|
else |
|
{ |
|
// Index file order is different from this CPU. |
|
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN |
|
// Index file order is big endian |
|
ret = ret_2; // We drop the 4 most significant bytes |
|
#else |
|
// Index file order is little endian |
|
ret = ret_1; // We drop the 4 most significant bytes |
|
#endif |
|
// We swap the result to host order. |
|
ret = kmail_swap_32(ret); |
|
} |
|
|
|
} |
|
break; |
|
} |
|
g_chunk_offset += l; |
|
} |
|
if(using_mmap) { |
|
g_chunk_length = 0; |
|
g_chunk = 0; |
|
} |
|
return ret; |
|
} |
|
|
|
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN |
|
// We need to use swab to swap bytes to network byte order |
|
#define memcpy_networkorder(to, from, len) swab((char *)(from), (char *)(to), len) |
|
#else |
|
// We're already in network byte order |
|
#define memcpy_networkorder(to, from, len) memcpy(to, from, len) |
|
#endif |
|
|
|
#define STORE_DATA_LEN(type, x, len, network_order) do { \ |
|
int len2 = (len > 256) ? 256 : len; \ |
|
if(csize < (length + (len2 + sizeof(short) + sizeof(MsgPartType)))) \ |
|
ret = (uchar *)realloc(ret, csize += len2+sizeof(short)+sizeof(MsgPartType)); \ |
|
quint32 t = (quint32) type; memcpy(ret+length, &t, sizeof(t)); \ |
|
quint16 l = len2; memcpy(ret+length+sizeof(t), &l, sizeof(l)); \ |
|
if (network_order) \ |
|
memcpy_networkorder(ret+length+sizeof(t)+sizeof(l), x, len2); \ |
|
else \ |
|
memcpy(ret+length+sizeof(t)+sizeof(l), x, len2); \ |
|
length += len2+sizeof(t)+sizeof(l); \ |
|
} while(0) |
|
#define STORE_DATA(type, x) STORE_DATA_LEN(type, &x, sizeof(x), false) |
|
|
|
//----------------------------------------------------------------------------- |
|
const uchar *KMMsgBase::asIndexString(int &length) const |
|
{ |
|
unsigned int csize = 256; |
|
static uchar *ret = 0; //different static buffer here for we may use the other buffer in the functions below |
|
if(!ret) |
|
ret = (uchar *)malloc(csize); |
|
length = 0; |
|
|
|
unsigned long tmp; |
|
QString tmp_str; |
|
|
|
//these is at the beginning because it is queried quite often |
|
tmp_str = msgIdMD5().trimmed(); |
|
STORE_DATA_LEN(MsgIdMD5Part, tmp_str.unicode(), tmp_str.length() * 2, true); |
|
tmp = 0; |
|
STORE_DATA(MsgLegacyStatusPart, tmp); |
|
|
|
//these are completely arbitrary order |
|
tmp_str = fromStrip().trimmed(); |
|
STORE_DATA_LEN(MsgFromPart, tmp_str.unicode(), tmp_str.length() * 2, true); |
|
tmp_str = tagString().trimmed(); |
|
STORE_DATA_LEN(MsgTagPart, tmp_str.unicode(), tmp_str.length() * 2, true); |
|
tmp_str = subject().trimmed(); |
|
STORE_DATA_LEN(MsgSubjectPart, tmp_str.unicode(), tmp_str.length() * 2, true); |
|
tmp_str = toStrip().trimmed(); |
|
STORE_DATA_LEN(MsgToPart, tmp_str.unicode(), tmp_str.length() * 2, true); |
|
tmp_str = replyToIdMD5().trimmed(); |
|
STORE_DATA_LEN(MsgReplyToIdMD5Part, tmp_str.unicode(), tmp_str.length() * 2, true); |
|
tmp_str = xmark().trimmed(); |
|
STORE_DATA_LEN(MsgXMarkPart, tmp_str.unicode(), tmp_str.length() * 2, true); |
|
tmp_str = fileName().trimmed(); |
|
STORE_DATA_LEN(MsgFilePart, tmp_str.unicode(), tmp_str.length() * 2, true); |
|
tmp = msgSize(); |
|
STORE_DATA(MsgSizePart, tmp); |
|
tmp = folderOffset(); |
|
STORE_DATA(MsgOffsetPart, tmp); |
|
tmp = date(); |
|
STORE_DATA(MsgDatePart, tmp); |
|
tmp = (signatureState() << 16) | encryptionState(); |
|
STORE_DATA(MsgCryptoStatePart, tmp); |
|
tmp = mdnSentState(); |
|
STORE_DATA(MsgMDNSentPart, tmp); |
|
|
|
tmp_str = replyToAuxIdMD5().trimmed(); |
|
STORE_DATA_LEN(MsgReplyToAuxIdMD5Part, tmp_str.unicode(), tmp_str.length() * 2, true); |
|
|
|
tmp_str = strippedSubjectMD5().trimmed(); |
|
STORE_DATA_LEN(MsgStrippedSubjectMD5Part, tmp_str.unicode(), tmp_str.length() * 2, true); |
|
|
|
tmp = mStatus.toQInt32(); |
|
STORE_DATA(MsgStatusPart, tmp); |
|
|
|
tmp = msgSizeServer(); |
|
STORE_DATA(MsgSizeServerPart, tmp); |
|
tmp = UID(); |
|
STORE_DATA(MsgUIDPart, tmp); |
|
|
|
return ret; |
|
} |
|
#undef STORE_DATA_LEN |
|
#undef STORE_DATA |
|
|
|
bool KMMsgBase::syncIndexString() const |
|
{ |
|
if(!dirty()) |
|
return true; |
|
int len; |
|
const uchar *buffer = asIndexString(len); |
|
if (len == mIndexLength) { |
|
Q_ASSERT(storage()->mIndexStream); |
|
KDE_fseek(storage()->mIndexStream, mIndexOffset, SEEK_SET); |
|
assert( mIndexOffset > 0 ); |
|
fwrite( buffer, len, 1, storage()->mIndexStream); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
static QStringList sReplySubjPrefixes, sForwardSubjPrefixes; |
|
static bool sReplaceSubjPrefix, sReplaceForwSubjPrefix; |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMsgBase::readConfig() |
|
{ |
|
KConfigGroup composerGroup( KMKernel::config(), "Composer" ); |
|
sReplySubjPrefixes = composerGroup.readEntry("reply-prefixes", QStringList()); |
|
if (sReplySubjPrefixes.isEmpty()) |
|
sReplySubjPrefixes << "Re\\s*:" << "Re\\[\\d+\\]:" << "Re\\d+:"; |
|
sReplaceSubjPrefix = |
|
composerGroup.readEntry( "replace-reply-prefix", true ); |
|
sForwardSubjPrefixes = composerGroup.readEntry("forward-prefixes", QStringList()); |
|
if (sForwardSubjPrefixes.isEmpty()) |
|
sForwardSubjPrefixes << "Fwd:" << "FW:"; |
|
sReplaceForwSubjPrefix = |
|
composerGroup.readEntry( "replace-forward-prefix", true ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// static |
|
QString KMMsgBase::stripOffPrefixes( const QString& str ) |
|
{ |
|
return replacePrefixes( str, sReplySubjPrefixes + sForwardSubjPrefixes, |
|
true, QString() ).trimmed(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// static |
|
QString KMMsgBase::replacePrefixes( const QString& str, |
|
const QStringList& prefixRegExps, |
|
bool replace, |
|
const QString& newPrefix ) |
|
{ |
|
bool recognized = false; |
|
// construct a big regexp that |
|
// 1. is anchored to the beginning of str (sans whitespace) |
|
// 2. matches at least one of the part regexps in prefixRegExps |
|
QString bigRegExp = QString::fromLatin1("^(?:\\s+|(?:%1))+\\s*") |
|
.arg( prefixRegExps.join(")|(?:") ); |
|
QRegExp rx( bigRegExp, Qt::CaseInsensitive ); |
|
if ( !rx.isValid() ) { |
|
kWarning(5006) <<"KMMessage::replacePrefixes(): bigRegExp = \"" |
|
<< bigRegExp << "\"\n" |
|
<< "prefix regexp is invalid!"; |
|
// try good ole Re/Fwd: |
|
recognized = str.startsWith( newPrefix ); |
|
} else { // valid rx |
|
QString tmp = str; |
|
if ( rx.indexIn( tmp ) == 0 ) { |
|
recognized = true; |
|
if ( replace ) |
|
return tmp.replace( 0, rx.matchedLength(), newPrefix + ' ' ); |
|
} |
|
} |
|
if ( !recognized ) |
|
return newPrefix + ' ' + str; |
|
else |
|
return str; |
|
} |
|
|
|
void KMMsgBase::setTagList( const QString &aTagStr ) |
|
{ |
|
setTagList( KMMessageTagList::split( ",", aTagStr ) ); |
|
} |
|
|
|
void KMMsgBase::setTagList( const KMMessageTagList &aTagList ) |
|
{ |
|
if ( !mTagList ) { |
|
mTagList = new KMMessageTagList( aTagList ); |
|
} else { |
|
if ( aTagList == *mTagList ) |
|
return; |
|
*mTagList = aTagList; |
|
} |
|
mTagList->prioritySort(); |
|
mDirty = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMsgBase::cleanSubject() const |
|
{ |
|
return cleanSubject( sReplySubjPrefixes + sForwardSubjPrefixes, |
|
true, QString() ).trimmed(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMsgBase::cleanSubject( const QStringList & prefixRegExps, |
|
bool replace, |
|
const QString & newPrefix ) const |
|
{ |
|
return KMMsgBase::replacePrefixes( subject(), prefixRegExps, replace, |
|
newPrefix ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMsgBase::forwardSubject() const { |
|
return cleanSubject( sForwardSubjPrefixes, sReplaceForwSubjPrefix, "Fwd:" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMsgBase::replySubject() const { |
|
return cleanSubject( sReplySubjPrefixes, sReplaceSubjPrefix, "Re:" ); |
|
}
|
|
|