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.
1484 lines
44 KiB
1484 lines
44 KiB
// kmmsgbase.cpp
|
|
|
|
#include <config.h>
|
|
|
|
#include "globalsettings.h"
|
|
#include "kmmsgbase.h"
|
|
|
|
#include "kmfolderindex.h"
|
|
#include "kmfolder.h"
|
|
#include "kmheaders.h"
|
|
#include "kmmsgdict.h"
|
|
#include "messageproperty.h"
|
|
using KMail::MessageProperty;
|
|
|
|
#include <kdebug.h>
|
|
#include <kglobal.h>
|
|
#include <kcharsets.h>
|
|
#include <kasciistringtools.h>
|
|
#include <kmdcodec.h>
|
|
#include <krfcdate.h>
|
|
|
|
#include <mimelib/mimepp.h>
|
|
#include <kmime_codecs.h>
|
|
|
|
#include <tqtextcodec.h>
|
|
#include <tqdeepcopy.h>
|
|
#include <tqregexp.h>
|
|
|
|
#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( KMMsgStatusUnknown )
|
|
{
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
KMMsgBase::~KMMsgBase()
|
|
{
|
|
MessageProperty::forget( this );
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
KMMsgBase& KMMsgBase::operator=(const KMMsgBase& other)
|
|
{
|
|
assign(&other);
|
|
return *this;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
KMMsgBase::KMMsgBase( const KMMsgBase& other )
|
|
{
|
|
assign( &other );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMsgBase::isMessage(void) const
|
|
{
|
|
return false;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void KMMsgBase::toggleStatus(const KMMsgStatus aStatus, int idx)
|
|
{
|
|
mDirty = true;
|
|
KMMsgStatus oldStatus = status();
|
|
if ( status() & aStatus ) {
|
|
mStatus &= ~aStatus;
|
|
} else {
|
|
mStatus |= aStatus;
|
|
// Ignored and Watched are toggleable, yet mutually exclusive.
|
|
// That is an arbitrary restriction on my part. HAR HAR HAR :) -till
|
|
if (aStatus == KMMsgStatusWatched)
|
|
mStatus &= ~KMMsgStatusIgnored;
|
|
if (aStatus == KMMsgStatusIgnored)
|
|
mStatus &= ~KMMsgStatusWatched;
|
|
if (aStatus == KMMsgStatusSpam)
|
|
mStatus &= ~KMMsgStatusHam;
|
|
if (aStatus == KMMsgStatusHam)
|
|
mStatus &= ~KMMsgStatusSpam;
|
|
}
|
|
if (storage()) {
|
|
if (idx < 0)
|
|
idx = storage()->find( this );
|
|
storage()->msgStatusChanged( oldStatus, status(), idx );
|
|
storage()->headerOfMsgChanged(this, idx);
|
|
}
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMsgBase::setStatus(const KMMsgStatus aStatus, int idx)
|
|
{
|
|
mDirty = true;
|
|
KMMsgStatus oldStatus = status();
|
|
switch (aStatus) {
|
|
case KMMsgStatusRead:
|
|
// Unset unread and new, set read
|
|
mStatus &= ~KMMsgStatusUnread;
|
|
mStatus &= ~KMMsgStatusNew;
|
|
mStatus |= KMMsgStatusRead;
|
|
break;
|
|
|
|
case KMMsgStatusUnread:
|
|
// unread overrides read
|
|
mStatus &= ~KMMsgStatusOld;
|
|
mStatus &= ~KMMsgStatusRead;
|
|
mStatus &= ~KMMsgStatusNew;
|
|
mStatus |= KMMsgStatusUnread;
|
|
break;
|
|
|
|
case KMMsgStatusOld:
|
|
// old can't be new or unread
|
|
mStatus &= ~KMMsgStatusNew;
|
|
mStatus &= ~KMMsgStatusUnread;
|
|
mStatus |= KMMsgStatusOld;
|
|
break;
|
|
|
|
case KMMsgStatusNew:
|
|
// new overrides old and read
|
|
mStatus &= ~KMMsgStatusOld;
|
|
mStatus &= ~KMMsgStatusRead;
|
|
mStatus &= ~KMMsgStatusUnread;
|
|
mStatus |= KMMsgStatusNew;
|
|
break;
|
|
|
|
case KMMsgStatusDeleted:
|
|
mStatus |= KMMsgStatusDeleted;
|
|
break;
|
|
|
|
case KMMsgStatusReplied:
|
|
mStatus |= KMMsgStatusReplied;
|
|
break;
|
|
|
|
case KMMsgStatusForwarded:
|
|
mStatus |= KMMsgStatusForwarded;
|
|
break;
|
|
|
|
case KMMsgStatusQueued:
|
|
mStatus |= KMMsgStatusQueued;
|
|
break;
|
|
|
|
case KMMsgStatusTodo:
|
|
mStatus |= KMMsgStatusTodo;
|
|
break;
|
|
|
|
case KMMsgStatusSent:
|
|
mStatus &= ~KMMsgStatusQueued;
|
|
mStatus &= ~KMMsgStatusUnread;
|
|
mStatus &= ~KMMsgStatusNew;
|
|
mStatus |= KMMsgStatusSent;
|
|
break;
|
|
|
|
case KMMsgStatusFlag:
|
|
mStatus |= KMMsgStatusFlag;
|
|
break;
|
|
|
|
// Watched and ignored are mutually exclusive
|
|
case KMMsgStatusWatched:
|
|
mStatus &= ~KMMsgStatusIgnored;
|
|
mStatus |= KMMsgStatusWatched;
|
|
break;
|
|
|
|
case KMMsgStatusIgnored:
|
|
mStatus &= ~KMMsgStatusWatched;
|
|
mStatus |= KMMsgStatusIgnored;
|
|
break;
|
|
// as are ham and spam
|
|
case KMMsgStatusSpam:
|
|
mStatus &= ~KMMsgStatusHam;
|
|
mStatus |= KMMsgStatusSpam;
|
|
break;
|
|
case KMMsgStatusHam:
|
|
mStatus &= ~KMMsgStatusSpam;
|
|
mStatus |= KMMsgStatusHam;
|
|
break;
|
|
case KMMsgStatusHasAttach:
|
|
mStatus &= ~KMMsgStatusHasNoAttach;
|
|
mStatus |= KMMsgStatusHasAttach;
|
|
break;
|
|
case KMMsgStatusHasNoAttach:
|
|
mStatus &= ~KMMsgStatusHasAttach;
|
|
mStatus |= KMMsgStatusHasNoAttach;
|
|
break;
|
|
default:
|
|
mStatus = aStatus;
|
|
break;
|
|
}
|
|
|
|
if ( oldStatus != mStatus && storage() ) {
|
|
if (idx < 0)
|
|
idx = storage()->find( this );
|
|
storage()->msgStatusChanged( oldStatus, status(), idx );
|
|
storage()->headerOfMsgChanged( this, idx );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMsgBase::setStatus(const char* aStatusStr, const char* aXStatusStr)
|
|
{
|
|
// first try to find status from "X-Status" field if given
|
|
if (aXStatusStr) {
|
|
if (strchr(aXStatusStr, 'N')) setStatus(KMMsgStatusNew);
|
|
if (strchr(aXStatusStr, 'U')) setStatus(KMMsgStatusUnread);
|
|
if (strchr(aXStatusStr, 'O')) setStatus(KMMsgStatusOld);
|
|
if (strchr(aXStatusStr, 'R')) setStatus(KMMsgStatusRead);
|
|
if (strchr(aXStatusStr, 'D')) setStatus(KMMsgStatusDeleted);
|
|
if (strchr(aXStatusStr, 'A')) setStatus(KMMsgStatusReplied);
|
|
if (strchr(aXStatusStr, 'F')) setStatus(KMMsgStatusForwarded);
|
|
if (strchr(aXStatusStr, 'Q')) setStatus(KMMsgStatusQueued);
|
|
if (strchr(aXStatusStr, 'K')) setStatus(KMMsgStatusTodo);
|
|
if (strchr(aXStatusStr, 'S')) setStatus(KMMsgStatusSent);
|
|
if (strchr(aXStatusStr, 'G')) setStatus(KMMsgStatusFlag);
|
|
if (strchr(aXStatusStr, 'P')) setStatus(KMMsgStatusSpam);
|
|
if (strchr(aXStatusStr, 'H')) setStatus(KMMsgStatusHam);
|
|
if (strchr(aXStatusStr, 'T')) setStatus(KMMsgStatusHasAttach);
|
|
if (strchr(aXStatusStr, 'C')) setStatus(KMMsgStatusHasNoAttach);
|
|
}
|
|
|
|
// Merge the contents of the "Status" field
|
|
if (aStatusStr) {
|
|
if ((aStatusStr[0]== 'R' && aStatusStr[1]== 'O') ||
|
|
(aStatusStr[0]== 'O' && aStatusStr[1]== 'R')) {
|
|
setStatus( KMMsgStatusOld );
|
|
setStatus( KMMsgStatusRead );
|
|
}
|
|
else if (aStatusStr[0] == 'R')
|
|
setStatus(KMMsgStatusRead);
|
|
else if (aStatusStr[0] == 'D')
|
|
setStatus(KMMsgStatusDeleted);
|
|
else
|
|
setStatus(KMMsgStatusNew);
|
|
}
|
|
}
|
|
|
|
|
|
void KMMsgBase::setEncryptionState( const KMMsgEncryptionState /*status*/, int idx )
|
|
{
|
|
//kdDebug(5006) << "***setEncryptionState1( " << status << " )" << endl;
|
|
mDirty = true;
|
|
if (storage())
|
|
storage()->headerOfMsgChanged(this, idx);
|
|
}
|
|
|
|
void KMMsgBase::setEncryptionStateChar( TQChar status, int idx )
|
|
{
|
|
//kdDebug(5006) << "***setEncryptionState2( " << (status.isNull() ? '?' : status.latin1()) << " )" << endl;
|
|
|
|
if( status.latin1() == (char)KMMsgEncryptionStateUnknown )
|
|
setEncryptionState( KMMsgEncryptionStateUnknown, idx );
|
|
else if( status.latin1() == (char)KMMsgNotEncrypted )
|
|
setEncryptionState( KMMsgNotEncrypted, idx );
|
|
else if( status.latin1() == (char)KMMsgPartiallyEncrypted )
|
|
setEncryptionState( KMMsgPartiallyEncrypted, idx );
|
|
else if( status.latin1() == (char)KMMsgFullyEncrypted )
|
|
setEncryptionState( KMMsgFullyEncrypted, idx );
|
|
else
|
|
setEncryptionState( KMMsgEncryptionStateUnknown, idx );
|
|
}
|
|
|
|
|
|
void KMMsgBase::setSignatureState( const KMMsgSignatureState /*status*/, int idx )
|
|
{
|
|
//kdDebug(5006) << "***setSignatureState1( " << status << " )" << endl;
|
|
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( TQChar status, int idx )
|
|
{
|
|
//kdDebug(5006) << "***setSignatureState2( " << (status.isNull() ? '?' : status.latin1()) << " )" << endl;
|
|
|
|
if( status.latin1() == (char)KMMsgSignatureStateUnknown )
|
|
setSignatureState( KMMsgSignatureStateUnknown, idx );
|
|
else if( status.latin1() == (char)KMMsgNotSigned )
|
|
setSignatureState( KMMsgNotSigned, idx );
|
|
else if( status.latin1() == (char)KMMsgPartiallySigned )
|
|
setSignatureState( KMMsgPartiallySigned,idx );
|
|
else if( status.latin1() == (char)KMMsgFullySigned )
|
|
setSignatureState( KMMsgFullySigned, idx );
|
|
else
|
|
setSignatureState( KMMsgSignatureStateUnknown, idx );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMsgBase::isUnread(void) const
|
|
{
|
|
KMMsgStatus st = status();
|
|
return (st & KMMsgStatusUnread && !(st & KMMsgStatusIgnored));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMsgBase::isNew(void) const
|
|
{
|
|
KMMsgStatus st = status();
|
|
return (st & KMMsgStatusNew && !(st & KMMsgStatusIgnored));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMsgBase::isOfUnknownStatus(void) const
|
|
{
|
|
KMMsgStatus st = status();
|
|
return (st == KMMsgStatusUnknown);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMsgBase::isOld(void) const
|
|
{
|
|
KMMsgStatus st = status();
|
|
return (st & KMMsgStatusOld);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMsgBase::isRead(void) const
|
|
{
|
|
KMMsgStatus st = status();
|
|
return (st & KMMsgStatusRead || st & KMMsgStatusIgnored);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMsgBase::isDeleted(void) const
|
|
{
|
|
KMMsgStatus st = status();
|
|
return (st & KMMsgStatusDeleted);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMsgBase::isReplied(void) const
|
|
{
|
|
KMMsgStatus st = status();
|
|
return (st & KMMsgStatusReplied);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMsgBase::isForwarded(void) const
|
|
{
|
|
KMMsgStatus st = status();
|
|
return (st & KMMsgStatusForwarded);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMsgBase::isQueued(void) const
|
|
{
|
|
KMMsgStatus st = status();
|
|
return (st & KMMsgStatusQueued);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMsgBase::isTodo(void) const
|
|
{
|
|
KMMsgStatus st = status();
|
|
return (st & KMMsgStatusTodo);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMsgBase::isSent(void) const
|
|
{
|
|
KMMsgStatus st = status();
|
|
return (st & KMMsgStatusSent);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMsgBase::isImportant(void) const
|
|
{
|
|
KMMsgStatus st = status();
|
|
return (st & KMMsgStatusFlag);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMsgBase::isWatched(void) const
|
|
{
|
|
KMMsgStatus st = status();
|
|
return (st & KMMsgStatusWatched);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMsgBase::isIgnored(void) const
|
|
{
|
|
KMMsgStatus st = status();
|
|
return (st & KMMsgStatusIgnored);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMsgBase::isSpam(void) const
|
|
{
|
|
KMMsgStatus st = status();
|
|
return (st & KMMsgStatusSpam);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool KMMsgBase::isHam(void) const
|
|
{
|
|
KMMsgStatus st = status();
|
|
return (st & KMMsgStatusHam);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
TQCString KMMsgBase::statusToStr(const KMMsgStatus status)
|
|
{
|
|
TQCString sstr;
|
|
if (status & KMMsgStatusNew) sstr += 'N';
|
|
if (status & KMMsgStatusUnread) sstr += 'U';
|
|
if (status & KMMsgStatusOld) sstr += 'O';
|
|
if (status & KMMsgStatusRead) sstr += 'R';
|
|
if (status & KMMsgStatusDeleted) sstr += 'D';
|
|
if (status & KMMsgStatusReplied) sstr += 'A';
|
|
if (status & KMMsgStatusForwarded) sstr += 'F';
|
|
if (status & KMMsgStatusQueued) sstr += 'Q';
|
|
if (status & KMMsgStatusTodo) sstr += 'K';
|
|
if (status & KMMsgStatusSent) sstr += 'S';
|
|
if (status & KMMsgStatusFlag) sstr += 'G';
|
|
if (status & KMMsgStatusWatched) sstr += 'W';
|
|
if (status & KMMsgStatusIgnored) sstr += 'I';
|
|
if (status & KMMsgStatusSpam) sstr += 'P';
|
|
if (status & KMMsgStatusHam) sstr += 'H';
|
|
if (status & KMMsgStatusHasAttach) sstr += 'T';
|
|
if (status & KMMsgStatusHasNoAttach) sstr += 'C';
|
|
|
|
return sstr;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
TQString KMMsgBase::statusToSortRank()
|
|
{
|
|
TQString sstr = "bcbbbbbbbb";
|
|
|
|
// put watched ones first, then normal ones, ignored ones last
|
|
if (status() & KMMsgStatusWatched) sstr[0] = 'a';
|
|
if (status() & KMMsgStatusIgnored) sstr[0] = 'c';
|
|
|
|
// Second level. One of new, old, read, unread
|
|
if (status() & KMMsgStatusNew) sstr[1] = 'a';
|
|
if (status() & KMMsgStatusUnread) sstr[1] = 'b';
|
|
//if (status() & KMMsgStatusOld) sstr[1] = 'c';
|
|
//if (status() & KMMsgStatusRead) sstr[1] = 'c';
|
|
|
|
// Third level. In somewhat arbitrary order.
|
|
if (status() & KMMsgStatusDeleted) sstr[2] = 'a';
|
|
if (status() & KMMsgStatusFlag) sstr[3] = 'a';
|
|
if (status() & KMMsgStatusReplied) sstr[4] = 'a';
|
|
if (status() & KMMsgStatusForwarded) sstr[5] = 'a';
|
|
if (status() & KMMsgStatusQueued) sstr[6] = 'a';
|
|
if (status() & KMMsgStatusSent) sstr[7] = 'a';
|
|
if (status() & KMMsgStatusHam) sstr[8] = 'a';
|
|
if (status() & KMMsgStatusSpam) sstr[8] = 'c';
|
|
if (status() & KMMsgStatusTodo) sstr[9] = 'a';
|
|
|
|
return sstr;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMsgBase::setDate(const TQCString& aDateStr)
|
|
{
|
|
setDate( KRFCDate::parseDate( aDateStr ) );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
TQString KMMsgBase::dateStr(void) const
|
|
{
|
|
time_t d = date();
|
|
return KMime::DateFormatter::formatDate(KMime::DateFormatter::Fancy, d);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
TQString KMMsgBase::skipKeyword(const TQString& aStr, TQChar sepChar,
|
|
bool* hasKeyword)
|
|
{
|
|
unsigned int i = 0, maxChars = 3;
|
|
TQString 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 TQTextCodec* KMMsgBase::codecForName(const TQCString& _str)
|
|
{
|
|
if (_str.isEmpty()) return 0;
|
|
TQCString codec = _str;
|
|
KPIM::kAsciiToLower(codec.data());
|
|
return KGlobal::charsets()->codecForName(codec);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
TQCString KMMsgBase::toUsAscii(const TQString& _str, bool *ok)
|
|
{
|
|
bool all_ok =true;
|
|
TQString result = _str;
|
|
int len = result.length();
|
|
for (int i = 0; i < len; i++)
|
|
if (result.at(i).unicode() >= 128) {
|
|
result.at(i) = '?';
|
|
all_ok = false;
|
|
}
|
|
if (ok)
|
|
*ok = all_ok;
|
|
return result.latin1();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
TQStringList KMMsgBase::supportedEncodings(bool usAscii)
|
|
{
|
|
TQStringList encodingNames = KGlobal::charsets()->availableEncodingNames();
|
|
TQStringList encodings;
|
|
TQMap<TQString,bool> mimeNames;
|
|
for (TQStringList::Iterator it = encodingNames.begin();
|
|
it != encodingNames.end(); it++)
|
|
{
|
|
TQTextCodec *codec = KGlobal::charsets()->codecForName(*it);
|
|
TQString mimeName = (codec) ? TQString(codec->mimeName()).lower() : (*it);
|
|
if (mimeNames.find(mimeName) == mimeNames.end())
|
|
{
|
|
encodings.append(KGlobal::charsets()->languageForEncoding(*it)
|
|
+ " ( " + mimeName + " )");
|
|
mimeNames.insert(mimeName, true);
|
|
}
|
|
}
|
|
encodings.sort();
|
|
if (usAscii) encodings.prepend(KGlobal::charsets()
|
|
->languageForEncoding("us-ascii") + " ( 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' ; }
|
|
|
|
TQCString unfold( const TQCString & header ) {
|
|
if ( header.isEmpty() )
|
|
return TQCString();
|
|
|
|
TQCString result( header.size() ); // 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;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
TQString KMMsgBase::decodeRFC2047String(const TQCString& aStr, TQCString prefCharset)
|
|
{
|
|
if ( aStr.isEmpty() )
|
|
return TQString::null;
|
|
|
|
const TQCString str = unfold( aStr );
|
|
|
|
if ( str.isEmpty() )
|
|
return TQString::null;
|
|
|
|
if ( str.find( "=?" ) < 0 ) {
|
|
if ( !prefCharset.isEmpty() ) {
|
|
if ( prefCharset == "us-ascii" ) {
|
|
// isn`t this foolproof?
|
|
return KMMsgBase::codecForName( "utf-8" )->toUnicode( str );
|
|
} else {
|
|
return KMMsgBase::codecForName( prefCharset )->toUnicode( str );
|
|
}
|
|
} else {
|
|
return KMMsgBase::codecForName( GlobalSettings::self()->
|
|
fallbackCharacterEncoding().latin1() )->toUnicode( str );
|
|
}
|
|
}
|
|
|
|
TQString result;
|
|
TQCString 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
|
|
TQCString 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 );
|
|
kdFatal( !c, 5006 ) << "No \"" << encoding << "\" codec!?" << endl;
|
|
|
|
TQByteArray in; in.setRawData( enc_start, pos - enc_start );
|
|
const TQByteArray enc = c->decode( in );
|
|
in.resetRawData( enc_start, pos - enc_start );
|
|
|
|
const TQTextCodec * 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 TQCString especials = "()<>@,;:\"/[]?.= \033";
|
|
|
|
TQCString KMMsgBase::encodeRFC2047Quoted( const TQCString & s, bool base64 ) {
|
|
const char * codecName = base64 ? "b" : "q" ;
|
|
const KMime::Codec * codec = KMime::Codec::codecForName( codecName );
|
|
kdFatal( !codec, 5006 ) << "No \"" << codecName << "\" found!?" << endl;
|
|
TQByteArray in; in.setRawData( s.data(), s.length() );
|
|
const TQByteArray result = codec->encode( in );
|
|
in.resetRawData( s.data(), s.length() );
|
|
return TQCString( result.data(), result.size() + 1 );
|
|
}
|
|
|
|
TQCString KMMsgBase::encodeRFC2047String(const TQString& _str,
|
|
const TQCString& charset)
|
|
{
|
|
static const TQString dontQuote = "\"()<>,@";
|
|
|
|
if (_str.isEmpty()) return TQCString();
|
|
if (charset == "us-ascii") return toUsAscii(_str);
|
|
|
|
TQCString cset;
|
|
if (charset.isEmpty())
|
|
{
|
|
cset = kmkernel->networkCodec()->mimeName();
|
|
KPIM::kAsciiToLower(cset.data());
|
|
}
|
|
else cset = charset;
|
|
|
|
const TQTextCodec *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;
|
|
TQCString 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.find(_str.at(p)) != -1))
|
|
start = p + 1;
|
|
if (_str.at(p).unicode() >= 128 || _str.at(p).unicode() < 32)
|
|
break;
|
|
p++;
|
|
}
|
|
if (breakLine || p < strLength)
|
|
{
|
|
while (dontQuote.find(_str.at(start)) != -1) start++;
|
|
stop = start;
|
|
while (stop < strLength && dontQuote.find(_str.at(stop)) == -1)
|
|
stop++;
|
|
result += _str.mid(pos, start - pos).latin1();
|
|
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;
|
|
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.findRev("\n ");
|
|
if (!result.mid(lastNewLine).stripWhiteSpace().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).latin1();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
TQCString KMMsgBase::encodeRFC2231String( const TQString& _str,
|
|
const TQCString& charset )
|
|
{
|
|
if ( _str.isEmpty() )
|
|
return TQCString();
|
|
|
|
TQCString cset;
|
|
if ( charset.isEmpty() )
|
|
{
|
|
cset = kmkernel->networkCodec()->mimeName();
|
|
KPIM::kAsciiToLower( cset.data() );
|
|
}
|
|
else
|
|
cset = charset;
|
|
const TQTextCodec *codec = codecForName( cset );
|
|
TQCString latin;
|
|
if ( charset == "us-ascii" )
|
|
latin = toUsAscii( _str );
|
|
else if ( codec )
|
|
latin = codec->fromUnicode( _str );
|
|
else
|
|
latin = _str.local8Bit();
|
|
|
|
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;
|
|
|
|
TQCString 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;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
TQString KMMsgBase::decodeRFC2231String(const TQCString& _str)
|
|
{
|
|
int p = _str.find('\'');
|
|
if (p < 0) return kmkernel->networkCodec()->toUnicode(_str);
|
|
|
|
TQCString charset = _str.left(p);
|
|
|
|
TQCString st = _str.mid(_str.findRev('\'') + 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.at(p) = ch * 16 + ch2;
|
|
st.remove( p+1, 2 );
|
|
}
|
|
p++;
|
|
}
|
|
TQString result;
|
|
const TQTextCodec * codec = codecForName( charset );
|
|
if ( !codec )
|
|
codec = kmkernel->networkCodec();
|
|
return codec->toUnicode( st );
|
|
}
|
|
|
|
TQCString KMMsgBase::extractRFC2231HeaderField( const TQCString &aStr, const TQCString &field )
|
|
{
|
|
int n=-1;
|
|
TQCString str;
|
|
bool found = false;
|
|
while ( n<=0 || found ) {
|
|
TQString pattern( field );
|
|
pattern += "[*]"; // match a literal * after the fieldname, as defined by RFC 2231
|
|
if ( n>=0 ) { // If n<0, check for fieldname*=..., otherwise for fieldname*n=
|
|
pattern += TQString::number(n) + "[*]?";
|
|
}
|
|
pattern += "=";
|
|
|
|
TQRegExp fnamePart( pattern, false );
|
|
int startPart = fnamePart.search( 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.find('"', startPart) - 1;
|
|
}
|
|
else {
|
|
endPart = aStr.find(';', startPart) - 1;
|
|
}
|
|
if (endPart < 0)
|
|
endPart = 32767;
|
|
str += aStr.mid( startPart, endPart-startPart+1).stripWhiteSpace();
|
|
}
|
|
n++;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
TQString KMMsgBase::base64EncodedMD5( const TQString & s, bool utf8 ) {
|
|
if (s.stripWhiteSpace().isEmpty()) return "";
|
|
if ( utf8 )
|
|
return base64EncodedMD5( s.stripWhiteSpace().utf8() ); // TQCString overload
|
|
else
|
|
return base64EncodedMD5( s.stripWhiteSpace().latin1() ); // const char * overload
|
|
}
|
|
|
|
TQString KMMsgBase::base64EncodedMD5( const TQCString & s ) {
|
|
if (s.stripWhiteSpace().isEmpty()) return "";
|
|
return base64EncodedMD5( s.stripWhiteSpace().data() );
|
|
}
|
|
|
|
TQString 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 );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
TQCString KMMsgBase::autoDetectCharset(const TQCString &_encoding, const TQStringList &encodingList, const TQString &text)
|
|
{
|
|
TQStringList charsets = encodingList;
|
|
if (!_encoding.isEmpty())
|
|
{
|
|
TQString currentCharset = TQString::fromLatin1(_encoding);
|
|
charsets.remove(currentCharset);
|
|
charsets.prepend(currentCharset);
|
|
}
|
|
|
|
TQStringList::ConstIterator it = charsets.begin();
|
|
for (; it != charsets.end(); ++it)
|
|
{
|
|
TQCString encoding = (*it).latin1();
|
|
if (encoding == "locale")
|
|
{
|
|
encoding = kmkernel->networkCodec()->mimeName();
|
|
KPIM::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 TQTextCodec *codec = KMMsgBase::codecForName(encoding);
|
|
if (!codec) {
|
|
kdDebug(5006) << "Auto-Charset: Something is wrong and I can not get a codec. [" << encoding << "]" << endl;
|
|
} 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
|
|
{
|
|
KMMsgStatus st = status();
|
|
if (st & KMMsgStatusHasAttach)
|
|
return KMMsgHasAttachment;
|
|
else if (st & KMMsgStatusHasNoAttach)
|
|
return KMMsgHasNoAttachment;
|
|
else
|
|
return KMMsgAttachmentUnknown;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
static void swapEndian(TQString &str)
|
|
{
|
|
uint len = str.length();
|
|
str = TQDeepCopy<TQString>(str);
|
|
TQChar *unicode = const_cast<TQChar*>( 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;
|
|
kdDebug( 5006 ) << "This should never happen.. "
|
|
<< __FILE__ << ":" << __LINE__ << endl;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
TQString KMMsgBase::getStringPart(MsgPartType t) const
|
|
{
|
|
retry:
|
|
TQString 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;
|
|
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=ftell(storage()->mIndexStream);
|
|
fseek(storage()->mIndexStream, mIndexOffset, SEEK_SET);
|
|
fread( g_chunk, mIndexLength, 1, storage()->mIndexStream);
|
|
fseek(storage()->mIndexStream, first_off, SEEK_SET);
|
|
}
|
|
|
|
MsgPartType type;
|
|
Q_UINT16 l;
|
|
while(g_chunk_offset < mIndexLength) {
|
|
Q_UINT32 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) {
|
|
kdDebug(5006) << "This should never happen.. " << __FILE__ << ":" << __LINE__ << endl;
|
|
if(using_mmap) {
|
|
g_chunk_length = 0;
|
|
g_chunk = 0;
|
|
}
|
|
storage()->recreateIndex();
|
|
goto retry;
|
|
}
|
|
if(type == t) {
|
|
// This works because the TQString constructor does a memcpy.
|
|
// Otherwise we would need to be concerned about the alignment.
|
|
if(l)
|
|
ret = TQString((TQChar *)(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.
|
|
|
|
#ifndef WORDS_BIGENDIAN
|
|
// #warning Byte order is little endian (swap is true)
|
|
swapEndian(ret);
|
|
#else
|
|
// #warning 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=ftell(storage()->mIndexStream);
|
|
fseek(storage()->mIndexStream, mIndexOffset, SEEK_SET);
|
|
fread( g_chunk, mIndexLength, 1, storage()->mIndexStream);
|
|
fseek(storage()->mIndexStream, first_off, SEEK_SET);
|
|
}
|
|
|
|
MsgPartType type;
|
|
Q_UINT16 l;
|
|
while (g_chunk_offset < mIndexLength) {
|
|
Q_UINT32 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) {
|
|
kdDebug(5006) << "This should never happen.. " << __FILE__ << ":" << __LINE__ << endl;
|
|
if(using_mmap) {
|
|
g_chunk_length = 0;
|
|
g_chunk = 0;
|
|
}
|
|
storage()->recreateIndex();
|
|
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
|
|
Q_UINT32 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
|
|
Q_UINT32 ret_1;
|
|
Q_UINT32 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.
|
|
#ifndef WORDS_BIGENDIAN
|
|
// 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.
|
|
#ifndef WORDS_BIGENDIAN
|
|
// 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;
|
|
}
|
|
|
|
#ifndef WORDS_BIGENDIAN
|
|
// 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)); \
|
|
Q_UINT32 t = (Q_UINT32) type; memcpy(ret+length, &t, sizeof(t)); \
|
|
Q_UINT16 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;
|
|
TQString tmp_str;
|
|
|
|
//these is at the beginning because it is queried quite often
|
|
tmp_str = msgIdMD5().stripWhiteSpace();
|
|
STORE_DATA_LEN(MsgIdMD5Part, tmp_str.unicode(), tmp_str.length() * 2, true);
|
|
tmp = mLegacyStatus;
|
|
STORE_DATA(MsgLegacyStatusPart, tmp);
|
|
|
|
//these are completely arbitrary order
|
|
tmp_str = fromStrip().stripWhiteSpace();
|
|
STORE_DATA_LEN(MsgFromPart, tmp_str.unicode(), tmp_str.length() * 2, true);
|
|
tmp_str = subject().stripWhiteSpace();
|
|
STORE_DATA_LEN(MsgSubjectPart, tmp_str.unicode(), tmp_str.length() * 2, true);
|
|
tmp_str = toStrip().stripWhiteSpace();
|
|
STORE_DATA_LEN(MsgToPart, tmp_str.unicode(), tmp_str.length() * 2, true);
|
|
tmp_str = replyToIdMD5().stripWhiteSpace();
|
|
STORE_DATA_LEN(MsgReplyToIdMD5Part, tmp_str.unicode(), tmp_str.length() * 2, true);
|
|
tmp_str = xmark().stripWhiteSpace();
|
|
STORE_DATA_LEN(MsgXMarkPart, tmp_str.unicode(), tmp_str.length() * 2, true);
|
|
tmp_str = fileName().stripWhiteSpace();
|
|
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().stripWhiteSpace();
|
|
STORE_DATA_LEN(MsgReplyToAuxIdMD5Part, tmp_str.unicode(), tmp_str.length() * 2, true);
|
|
|
|
tmp_str = strippedSubjectMD5().stripWhiteSpace();
|
|
STORE_DATA_LEN(MsgStrippedSubjectMD5Part, tmp_str.unicode(), tmp_str.length() * 2, true);
|
|
|
|
tmp = status();
|
|
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);
|
|
fseek(storage()->mIndexStream, mIndexOffset, SEEK_SET);
|
|
assert( mIndexOffset > 0 );
|
|
fwrite( buffer, len, 1, storage()->mIndexStream);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static TQStringList sReplySubjPrefixes, sForwardSubjPrefixes;
|
|
static bool sReplaceSubjPrefix, sReplaceForwSubjPrefix;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void KMMsgBase::readConfig()
|
|
{
|
|
KConfigGroup composerGroup( KMKernel::config(), "Composer" );
|
|
sReplySubjPrefixes = composerGroup.readListEntry("reply-prefixes", ',');
|
|
if (sReplySubjPrefixes.isEmpty())
|
|
sReplySubjPrefixes << "Re\\s*:" << "Re\\[\\d+\\]:" << "Re\\d+:";
|
|
sReplaceSubjPrefix = composerGroup.readBoolEntry("replace-reply-prefix", true);
|
|
sForwardSubjPrefixes = composerGroup.readListEntry("forward-prefixes", ',');
|
|
if (sForwardSubjPrefixes.isEmpty())
|
|
sForwardSubjPrefixes << "Fwd:" << "FW:";
|
|
sReplaceForwSubjPrefix = composerGroup.readBoolEntry("replace-forward-prefix", true);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// static
|
|
TQString KMMsgBase::stripOffPrefixes( const TQString& str )
|
|
{
|
|
return replacePrefixes( str, sReplySubjPrefixes + sForwardSubjPrefixes,
|
|
true, TQString::null ).stripWhiteSpace();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// static
|
|
TQString KMMsgBase::replacePrefixes( const TQString& str,
|
|
const TQStringList& prefixRegExps,
|
|
bool replace,
|
|
const TQString& 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
|
|
TQString bigRegExp = TQString::fromLatin1("^(?:\\s+|(?:%1))+\\s*")
|
|
.arg( prefixRegExps.join(")|(?:") );
|
|
TQRegExp rx( bigRegExp, false /*case insens.*/ );
|
|
if ( !rx.isValid() ) {
|
|
kdWarning(5006) << "KMMessage::replacePrefixes(): bigRegExp = \""
|
|
<< bigRegExp << "\"\n"
|
|
<< "prefix regexp is invalid!" << endl;
|
|
// try good ole Re/Fwd:
|
|
recognized = str.startsWith( newPrefix );
|
|
} else { // valid rx
|
|
TQString tmp = str;
|
|
if ( rx.search( tmp ) == 0 ) {
|
|
recognized = true;
|
|
if ( replace )
|
|
return tmp.replace( 0, rx.matchedLength(), newPrefix + ' ' );
|
|
}
|
|
}
|
|
if ( !recognized )
|
|
return newPrefix + ' ' + str;
|
|
else
|
|
return str;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
TQString KMMsgBase::cleanSubject() const
|
|
{
|
|
return cleanSubject( sReplySubjPrefixes + sForwardSubjPrefixes,
|
|
true, TQString::null ).stripWhiteSpace();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
TQString KMMsgBase::cleanSubject( const TQStringList & prefixRegExps,
|
|
bool replace,
|
|
const TQString & newPrefix ) const
|
|
{
|
|
return KMMsgBase::replacePrefixes( subject(), prefixRegExps, replace,
|
|
newPrefix );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
TQString KMMsgBase::forwardSubject() const {
|
|
return cleanSubject( sForwardSubjPrefixes, sReplaceForwSubjPrefix, "Fwd:" );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
TQString KMMsgBase::replySubject() const {
|
|
return cleanSubject( sReplySubjPrefixes, sReplaceSubjPrefix, "Re:" );
|
|
}
|