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.
703 lines
17 KiB
703 lines
17 KiB
// kmmsgbase.cpp |
|
|
|
#include <kdebug.h> |
|
#include <kglobal.h> |
|
#include <klocale.h> |
|
|
|
#include "kmmsgbase.h" |
|
#include <mimelib/mimepp.h> |
|
#include <qtextcodec.h> |
|
#include <qregexp.h> |
|
#include <kmfolder.h> |
|
|
|
#include <ctype.h> |
|
#include <stdlib.h> |
|
|
|
#define NUM_STATUSLIST 9 |
|
static KMMsgStatus sStatusList[NUM_STATUSLIST] = |
|
{ |
|
KMMsgStatusDeleted, KMMsgStatusNew, |
|
KMMsgStatusUnread, KMMsgStatusOld, |
|
KMMsgStatusRead, KMMsgStatusReplied, |
|
KMMsgStatusSent, KMMsgStatusQueued, |
|
KMMsgStatusUnknown /* "Unknown" must be at the *end* of the list */ |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
KMMsgBase::KMMsgBase(KMFolder* aParent) |
|
{ |
|
mParent = aParent; |
|
mDirty = FALSE; |
|
mMsgSize = 0; |
|
mFolderOffset = 0; |
|
mStatus = KMMsgStatusNew; |
|
mDate = 0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
KMMsgBase::~KMMsgBase() |
|
{ |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMsgBase::assign(const KMMsgBase* other) |
|
{ |
|
mParent = other->mParent; |
|
mDirty = other->mDirty; |
|
mMsgSize = other->mMsgSize; |
|
mFolderOffset = other->mFolderOffset; |
|
mStatus = other->mStatus; |
|
mDate = other->mDate; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
KMMsgBase& KMMsgBase::operator=(const KMMsgBase& other) |
|
{ |
|
assign(&other); |
|
return *this; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
bool KMMsgBase::isMessage(void) const |
|
{ |
|
return FALSE; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMsgBase::setStatus(const KMMsgStatus aStatus) |
|
{ |
|
if (mStatus == aStatus) return; |
|
if (mParent) mParent->msgStatusChanged( mStatus, aStatus ); |
|
mStatus = aStatus; |
|
mDirty = TRUE; |
|
if (mParent) mParent->headerOfMsgChanged(this); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMsgBase::setStatus(const char* aStatusStr, const char* aXStatusStr) |
|
{ |
|
int i; |
|
|
|
mStatus = KMMsgStatusUnknown; |
|
|
|
// first try to find status from "X-Status" field if given |
|
if (aXStatusStr) for (i=0; i<NUM_STATUSLIST-1; i++) |
|
{ |
|
if (strchr(aXStatusStr, (char)sStatusList[i])) |
|
{ |
|
mStatus = sStatusList[i]; |
|
break; |
|
} |
|
} |
|
|
|
// if not successful then use the "Status" field |
|
if (mStatus == KMMsgStatusUnknown) |
|
{ |
|
if (aStatusStr && |
|
((aStatusStr[0]=='R' && aStatusStr[1]=='O') || |
|
(aStatusStr[0]=='O' && aStatusStr[1]=='R'))) |
|
mStatus=KMMsgStatusOld; |
|
else if (aStatusStr && aStatusStr[0]=='R') mStatus=KMMsgStatusRead; |
|
else if (aStatusStr && aStatusStr[0]=='D') mStatus=KMMsgStatusDeleted; |
|
else mStatus=KMMsgStatusNew; |
|
} |
|
|
|
mDirty = TRUE; |
|
if (mParent) mParent->headerOfMsgChanged(this); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
KMMsgStatus KMMsgBase::status(void) const |
|
{ |
|
return mStatus; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
bool KMMsgBase::isUnread(void) const |
|
{ |
|
KMMsgStatus st = status(); |
|
return (st==KMMsgStatusNew || st==KMMsgStatusUnread); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
bool KMMsgBase::isNew(void) const |
|
{ |
|
KMMsgStatus st = status(); |
|
return (st==KMMsgStatusNew); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
const char* KMMsgBase::statusToStr(KMMsgStatus aStatus) |
|
{ |
|
static char sstr[2]; |
|
|
|
sstr[0] = (char)aStatus; |
|
sstr[1] = '\0'; |
|
|
|
return sstr; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMsgBase::setDate(time_t aUnixTime) |
|
{ |
|
if (mDate == aUnixTime) return; |
|
mDate = aUnixTime; |
|
mDirty = TRUE; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMsgBase::setDate(const char* aDateStr) |
|
{ |
|
DwDateTime dwDate; |
|
|
|
dwDate.FromString(aDateStr); |
|
dwDate.Parse(); |
|
mDate = dwDate.AsUnixTime(); |
|
mDirty = TRUE; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
time_t KMMsgBase::date(void) const |
|
{ |
|
return mDate; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
const QString KMMsgBase::dateStr(void) const |
|
{ |
|
return ctime(&mDate); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
const QCString KMMsgBase::asIndexString(void) const |
|
{ |
|
int i, len; |
|
QCString str; |
|
unsigned long dateTen = date(); |
|
// dateTen %= 10000000000; // In index only 10 chars are reserved for the date |
|
// This is nonsense because 10000000000 is bigger than the highest unsigned |
|
// long. (Or is there any compiler that defines unsigned long as something |
|
// really huge?? ) |
|
|
|
QCString a(subject().utf8()); |
|
a.truncate(100); |
|
QCString b(fromStrip().utf8()); |
|
b.truncate(50); |
|
QCString c(toStrip().utf8()); |
|
c.truncate(47); |
|
QCString d((const char*)replyToIdMD5()); |
|
d.truncate(22); |
|
QCString e((const char*)msgIdMD5()); |
|
e.truncate(22); |
|
|
|
// don't forget to change indexStringLength() below !! |
|
str.sprintf("%c %-.9lu %-.9lu %-.10lu %-3.3s ", |
|
(char)status(), folderOffset(), msgSize(), dateTen, |
|
(const char*)xmark() ); |
|
if (str.length() != 37) |
|
kdDebug() << "Invalid length " << endl; |
|
str += a.rightJustify( 100, ' ' ); |
|
str += " "; |
|
str += b.rightJustify( 50, ' ' ); |
|
str += " "; |
|
str += c.rightJustify( 50, ' ' ); |
|
str += " "; |
|
str += d.rightJustify( 22, ' ' ); |
|
str += " "; |
|
str += e.rightJustify( 22, ' ' ); |
|
|
|
len = str.length(); |
|
for (i=0; i<len; i++) |
|
if (str[i] < ' ' && str[i] >= 0) |
|
str[i] = ' '; |
|
|
|
if (str.length() != 285) { |
|
kdDebug() << QString( "Error invalid index entry %1").arg(str.length()) << endl; |
|
kdDebug() << str << endl; |
|
} |
|
return str; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
int KMMsgBase::indexStringLength(void) |
|
{ |
|
//return 237; |
|
// return 338; //sven (+ 100 chars to + one space, right? |
|
// return 339; //sanders (use 10 digits for the date we need this in 2001!) |
|
// return 541; //sanders include Reply-To and Message-Id for threading |
|
return 285; // sanders strip from and to and use MD5 on Ids |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMsgBase::skipKeyword(const QString& aStr, char sepChar, |
|
bool* hasKeyword) |
|
{ |
|
int i, maxChars=3; |
|
const char *pos, *str = aStr.data(); |
|
|
|
if (!str) return 0; |
|
|
|
while (*str==' ') |
|
str++; |
|
if (hasKeyword) *hasKeyword=FALSE; |
|
|
|
for (i=0,pos=str; *pos && i<maxChars; pos++,i++) |
|
{ |
|
if (*pos < 'A' || *pos == sepChar) break; |
|
} |
|
|
|
if (i>1 && *pos == sepChar) // skip following spaces too |
|
{ |
|
for (pos++; *pos==' '; pos++) |
|
; |
|
if (hasKeyword) *hasKeyword=TRUE; |
|
return pos; |
|
} |
|
return str; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QTextCodec* KMMsgBase::codecForName(const QString& _str) |
|
{ |
|
if (_str.isEmpty()) return NULL; |
|
if (_str.lower() == "shift_jis" || _str.lower() == "shift-jis") |
|
return QTextCodec::codecForName("sjis"); |
|
return QTextCodec::codecForName(_str.lower().replace( |
|
QRegExp("windows"), "cp") ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
const QCString KMMsgBase::toUsAscii(const QString& _str) |
|
{ |
|
QString result = _str.copy(); |
|
int len = result.length(); |
|
for (int i = 0; i < len; i++) |
|
if (result.at(i).unicode() >= 128) result.at(i) = '?'; |
|
return result.latin1(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
const QString KMMsgBase::decodeRFC2047String(const QString& _str) |
|
{ |
|
QCString aStr = _str.ascii(); |
|
QString result; |
|
QCString charset; |
|
char *pos, *beg, *end, *mid; |
|
QCString cstr; |
|
QString str; |
|
char encoding, ch; |
|
bool valid; |
|
const int maxLen=200; |
|
int i; |
|
|
|
if (aStr.find("=?") < 0) return QString::fromLocal8Bit(aStr); |
|
|
|
for (pos=aStr.data(); *pos; pos++) |
|
{ |
|
if (pos[0]!='=' || pos[1]!='?') |
|
{ |
|
result += *pos; |
|
continue; |
|
} |
|
beg = pos+2; |
|
end = beg; |
|
valid = TRUE; |
|
// parse charset name |
|
charset = ""; |
|
for (i=2,pos+=2; i<maxLen && (*pos!='?'&&(*pos==' '||ispunct(*pos)||isalnum(*pos))); i++) |
|
{ |
|
charset += *pos; |
|
pos++; |
|
} |
|
if (*pos!='?' || i<4 || i>=maxLen) valid = FALSE; |
|
else |
|
{ |
|
// get encoding and check delimiting question marks |
|
encoding = toupper(pos[1]); |
|
if (pos[2]!='?' || (encoding!='Q' && encoding!='B')) |
|
valid = FALSE; |
|
pos+=3; |
|
i+=3; |
|
} |
|
if (valid) |
|
{ |
|
mid = pos; |
|
// search for end of encoded part |
|
while (i<maxLen && *pos && !(*pos=='?' && *(pos+1)=='=')) |
|
{ |
|
i++; |
|
pos++; |
|
} |
|
end = pos+2;//end now points to the first char after the encoded string |
|
if (i>=maxLen || !*pos) valid = FALSE; |
|
} |
|
if (valid) |
|
{ |
|
ch = *pos; |
|
*pos = '\0'; |
|
str = QString(mid).left((int)(mid - pos - 1)); |
|
if (encoding == 'Q') |
|
{ |
|
// decode quoted printable text |
|
for (i=str.length()-1; i>=0; i--) |
|
if (str[i]=='_') str[i]=' '; |
|
cstr = decodeQuotedPrintable(str); |
|
} |
|
else |
|
{ |
|
// decode base64 text |
|
cstr = decodeBase64(str); |
|
} |
|
QTextCodec *codec = codecForName(charset); |
|
if (!codec) codec = codecForName(KGlobal::locale()->charset()); |
|
if (codec) str = codec->toUnicode(QString(cstr)); // Workaround |
|
else str = QString::fromLocal8Bit(cstr); |
|
|
|
// Workaround for bug in QT-2.2.2 |
|
// the utf-8 QTextCodec adds a 0x0000 at the end of the string |
|
if (str.at(str.length() - 1).isNull()) str = str.left(str.length() - 1); |
|
|
|
*pos = ch; |
|
result += str; |
|
// for (i=0; i < (int)str.length(); i++) |
|
// result += (char)(QChar)str[i]; |
|
|
|
pos = end -1; |
|
} |
|
else |
|
{ |
|
//result += "=?"; |
|
//pos = beg -1; // because pos gets increased shortly afterwards |
|
pos = beg - 2; |
|
result += *pos++; |
|
result += *pos; |
|
} |
|
} |
|
return result.replace(QRegExp("\n[ \t]"),""); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
const QString especials = "()<>@,;:\"/[]?.= \033"; |
|
const QString dontQuote = "\"()<>"; |
|
|
|
const QString KMMsgBase::encodeRFC2047Quoted(const QString& aStr, bool base64) |
|
{ |
|
if (base64) return encodeBase64(aStr).copy().replace(QRegExp("\n"),""); |
|
QString result; |
|
unsigned char ch, hex; |
|
for (unsigned int i = 0; i < aStr.length(); i++) |
|
{ |
|
ch = aStr.at(i); |
|
if (ch >= 128 || especials.find(aStr.at(i)) != -1) |
|
{ |
|
result += "="; |
|
hex = ((ch & 0xF0) >> 4) + 48; |
|
if (hex >= 58) hex += 7; |
|
result += hex; |
|
hex = (ch & 0x0F) + 48; |
|
if (hex >= 58) hex += 7; |
|
result += hex; |
|
} else { |
|
result += aStr.at(i); |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
|
|
const QString KMMsgBase::encodeRFC2047String(const QString& _str, |
|
const QString& charset) |
|
{ |
|
if (_str.isEmpty()) return _str; |
|
if (charset == "us-ascii") return toUsAscii(_str); |
|
|
|
QString cset; |
|
if (charset.isEmpty()) cset = KGlobal::locale()->charset(); |
|
else cset = charset; |
|
QTextCodec *codec = codecForName(cset); |
|
if (!codec) codec = QTextCodec::codecForLocale(); |
|
|
|
unsigned int nonAscii = 0; |
|
for (unsigned int i = 0; i < _str.length(); i++) |
|
if (_str.at(i).unicode() >= 128) nonAscii++; |
|
bool useBase64 = (nonAscii * 6 > _str.length()); |
|
|
|
unsigned int start, stop, p, pos = 0, encLength; |
|
QString result = QString(); |
|
bool breakLine; |
|
const unsigned int maxLen = 75 - 7 - cset.length(); |
|
|
|
while (pos < _str.length()) |
|
{ |
|
start = pos; p = pos; |
|
while (p < _str.length()) |
|
{ |
|
if (_str.at(p) == ' ') start = p + 1; |
|
if (_str.at(p).unicode() >= 128 || _str.at(p) < ' ') break; |
|
p++; |
|
} |
|
if (p < _str.length()) |
|
{ |
|
while (dontQuote.find(_str.at(start)) != -1) start++; |
|
stop = start; |
|
while (stop < _str.length() && dontQuote.find(_str.at(stop)) == -1) |
|
stop++; |
|
result += _str.mid(pos, start - pos); |
|
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.isEmpty() && !(result.right(2) == "\n ") && |
|
result.length() - result.findRev("\n ") + 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); |
|
break; |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
const QString KMMsgBase::encodeRFC2231String(const QString& _str, |
|
const QString& charset) |
|
{ |
|
if (_str.isEmpty()) return _str; |
|
QString cset; |
|
if (charset.isEmpty()) cset = KGlobal::locale()->charset(); |
|
else cset = charset; |
|
QTextCodec *codec = codecForName(cset); |
|
QCString latin; |
|
if (charset == "us-ascii") latin = toUsAscii(_str); |
|
else if (codec) latin = codec->fromUnicode(_str); |
|
else latin = _str.local8Bit(); |
|
|
|
char *l = latin.data(); |
|
char hexcode; |
|
int i; |
|
bool quote; |
|
while (*l) |
|
{ |
|
if (*l < 32) break; |
|
l++; |
|
} |
|
if (!*l) return latin; |
|
QString result = cset; |
|
result += QString("''"); |
|
l = latin.data(); |
|
while (*l) |
|
{ |
|
quote = *l < 0; |
|
for (i = 0; i < 17; i++) if (*l == especials[i]) quote = true; |
|
if (quote) |
|
{ |
|
result += "%"; |
|
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; |
|
} |
|
l++; |
|
} |
|
return result; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
const QString KMMsgBase::decodeRFC2231String(const QString& _str) |
|
{ |
|
int p = _str.find("'"); |
|
if (p < 0) return QString::fromLocal8Bit(_str); |
|
|
|
QString charset = _str.left(p); |
|
|
|
QCString st = _str.mid(_str.findRev("'") + 1).ascii(); |
|
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++; |
|
} |
|
QString result; |
|
QTextCodec *codec = codecForName(charset); |
|
if (!codec) codec = codecForName(KGlobal::locale()->charset()); |
|
if (codec) result = codec->toUnicode(st); |
|
else result = QString::fromLocal8Bit(st); |
|
|
|
return result; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
const QString KMMsgBase::decodeQuotedPrintableString(const QString& aStr) |
|
{ |
|
#ifdef BROKEN |
|
static QString result; |
|
int start, beg, mid, end; |
|
end = 0; // Remove compiler warning; |
|
|
|
start = 0; |
|
end = 0; |
|
result = ""; |
|
|
|
while (1) |
|
{ |
|
beg = aStr.find("=?", start); |
|
if (beg < 0) |
|
{ |
|
// no more suspicious string parts found -- done |
|
result += aStr.mid(start, 32767); |
|
break; |
|
} |
|
|
|
if (beg > start) result += aStr.mid(start, beg-start); |
|
mid = aStr.find("?Q?", beg+2); |
|
if (mid>beg) end = aStr.find("?=", mid+3); |
|
if (mid < 0 || end < 0) |
|
{ |
|
// no quoted printable part -- skip it |
|
result += "=?"; |
|
start += 2; |
|
continue; |
|
} |
|
if (aStr[mid+3]=='_' ) |
|
{ |
|
result += ' '; |
|
mid++; |
|
} |
|
else if (aStr[mid+3]==' ') mid++; |
|
|
|
if (end-mid-3 > 0) |
|
result += decodeQuotedPrintable(aStr.mid(mid+3, end-mid-3).data()); |
|
start = end+2; |
|
} |
|
return result; |
|
#else |
|
return decodeRFC2047String(aStr); |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
const QString KMMsgBase::decodeQuotedPrintable(const QString& aStr) |
|
{ |
|
QString bStr = aStr; |
|
if (aStr.isNull()) |
|
bStr = ""; |
|
|
|
DwString dwsrc(bStr.data()); |
|
DwString dwdest; |
|
|
|
DwDecodeQuotedPrintable(dwsrc, dwdest); |
|
return dwdest.c_str(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
const QString KMMsgBase::encodeQuotedPrintable(const QString& aStr) |
|
{ |
|
QString bStr = aStr; |
|
if (aStr.isNull()) |
|
bStr = ""; |
|
|
|
DwString dwsrc(bStr.data(), bStr.length()); |
|
DwString dwdest; |
|
QString result; |
|
|
|
DwEncodeQuotedPrintable(dwsrc, dwdest); |
|
result = dwdest.c_str(); |
|
return result; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
const QString KMMsgBase::decodeBase64(const QString& aStr) |
|
{ |
|
QString bStr = aStr; |
|
if (aStr.isNull()) |
|
bStr = ""; |
|
while (bStr.length() < 16) bStr += "="; |
|
|
|
DwString dwsrc(bStr.data(), bStr.length()); |
|
DwString dwdest; |
|
QString result; |
|
|
|
DwDecodeBase64(dwsrc, dwdest); |
|
result = dwdest.c_str(); |
|
return result; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
const QString KMMsgBase::encodeBase64(const QString& aStr) |
|
{ |
|
QString bStr = aStr; |
|
if (aStr.isNull()) |
|
bStr = ""; |
|
|
|
DwString dwsrc(bStr.data(), bStr.length()); |
|
DwString dwdest; |
|
QString result; |
|
|
|
DwEncodeBase64(dwsrc, dwdest); |
|
result = dwdest.c_str(); |
|
return result; |
|
}
|
|
|