// kmmsgbase.cpp #include #include #include #include #include #include #include #include #include #include #include #include #if QT_VERSION < 300 # define Q_ASSERT ASSERT #endif #define NUM_STATUSLIST 10 static KMMsgStatus sStatusList[NUM_STATUSLIST] = { KMMsgStatusDeleted, KMMsgStatusNew, KMMsgStatusUnread, KMMsgStatusOld, KMMsgStatusRead, KMMsgStatusReplied, KMMsgStatusSent, KMMsgStatusQueued, KMMsgStatusFlag, KMMsgStatusUnknown /* "Unknown" must be at the *end* of the list */ }; //----------------------------------------------------------------------------- KMMsgBase::KMMsgBase(KMFolder* aParent) { mParent = aParent; mDirty = FALSE; mIndexOffset = mIndexLength = 0; } //----------------------------------------------------------------------------- KMMsgBase::~KMMsgBase() { } //----------------------------------------------------------------------------- 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; } //----------------------------------------------------------------------------- bool KMMsgBase::isMessage(void) const { return FALSE; } //----------------------------------------------------------------------------- void KMMsgBase::setStatus(const KMMsgStatus aStatus) { if (mParent) mParent->msgStatusChanged( status(), aStatus ); mDirty = TRUE; if (mParent) mParent->headerOfMsgChanged(this); } //----------------------------------------------------------------------------- void KMMsgBase::setStatus(const char* aStatusStr, const char* aXStatusStr) { setStatus(KMMsgStatusUnknown); // first try to find status from "X-Status" field if given if (aXStatusStr) { for (int i=0; icodecForName(_str.lower()); } //----------------------------------------------------------------------------- const QCString 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.at(i) = '?'; all_ok = false; } if (ok) *ok = all_ok; return result.latin1(); } //----------------------------------------------------------------------------- QStringList KMMsgBase::supportedEncodings(bool usAscii) { QStringList encodingNames = KGlobal::charsets()->availableEncodingNames(); QStringList encodings; QMap mimeNames; for (QStringList::Iterator it = encodingNames.begin(); it != encodingNames.end(); it++) { QTextCodec *codec = KGlobal::charsets()->codecForName(*it); QString mimeName = (codec) ? QString(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; } //----------------------------------------------------------------------------- QString KMMsgBase::decodeRFC2047String(const QCString& aStr) { QString result; QCString charset; char *pos, *beg, *end, *mid; QCString str, cstr, LWSP_buffer; char encoding, ch; bool valid, lastWasEncodedWord=FALSE; const int maxLen=200; int i; if (aStr.find("=?") < 0) { QString str = QString::fromLocal8Bit(aStr); if (str.find('\n') == -1) return str; return str.replace(QRegExp("\n[\t ]")," "); } for (pos=aStr.data(); *pos; pos++) { // line unfolding if ( pos[0] == '\r' && pos[1] == '\n' ) { pos++; continue; } if ( pos[0] == '\n' ) continue; // collect LWSP after encoded-words, // because we might need to throw it out // (when the next word is an encoded-word) if ( lastWasEncodedWord && ( pos[0] == ' ' || pos[0] == '\t' ) ) { 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 beg = pos+2; end = beg; valid = TRUE; // parse charset name charset = ""; for (i=2,pos+=2; 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) valid = FALSE; } if (valid) { // valid encoding: decode and throw away separating LWSP ch = *pos; *pos = '\0'; str = QCString(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 = KGlobal::locale()->codecForEncoding(); if (codec) result += codec->toUnicode(cstr); else result += QString::fromLocal8Bit(cstr); lastWasEncodedWord = TRUE; *pos = ch; pos = end -1; } else { // invalid encoding, keep separating LWSP. //result += "=?"; //pos = beg -1; // because pos gets increased shortly afterwards pos = beg - 2; result += LWSP_buffer; result += *pos++; result += *pos; lastWasEncodedWord = FALSE; } LWSP_buffer = 0; } return result; } //----------------------------------------------------------------------------- const QString especials = "()<>@,;:\"/[]?.= \033"; const QString dontQuote = "\"()<>,"; QCString KMMsgBase::encodeRFC2047Quoted(const QCString& aStr, bool base64) { if (base64) return encodeBase64(aStr).replace(QRegExp("\n"),""); QCString result; unsigned char ch, hex; for (unsigned int i = 0; i < aStr.length(); i++) { ch = aStr.at(i); if (ch >= 128 || ch == '_' || 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; } QCString KMMsgBase::encodeRFC2047String(const QString& _str, const QCString& charset) { if (_str.isEmpty()) return QCString(); if (charset == "us-ascii") return toUsAscii(_str); QCString cset; if (charset.isEmpty()) cset = QCString(KGlobal::locale()->codecForEncoding()->mimeName()).lower(); 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; QCString result; 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) == ' ' || dontQuote.find(_str.at(p)) != -1) 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).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; } //----------------------------------------------------------------------------- QCString KMMsgBase::encodeRFC2231String(const QString& _str, const QCString& charset) { if (_str.isEmpty()) return QCString(); QCString cset; if (charset.isEmpty()) cset = QCString(KGlobal::locale()->codecForEncoding()->mimeName()).lower(); 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; QCString result = cset + "''"; 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; } //----------------------------------------------------------------------------- QString KMMsgBase::decodeRFC2231String(const QCString& _str) { int p = _str.find("'"); if (p < 0) return QString::fromLocal8Bit(_str); QCString charset = _str.left(p); QCString 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++; } QString result; QTextCodec *codec = codecForName(charset); if (!codec) codec = KGlobal::locale()->codecForEncoding(); if (codec) result = codec->toUnicode(st); else result = QString::fromLocal8Bit(st); return result; } //----------------------------------------------------------------------------- QCString KMMsgBase::autoDetectCharset(const QCString &_encoding, const QStringList &encodingList, const QString &text) { QStringList charsets = encodingList; if (!_encoding.isEmpty()) { QString currentCharset = QString::fromLatin1(_encoding); charsets.remove(currentCharset); charsets.prepend(currentCharset); } QStringList::ConstIterator it = charsets.begin(); for (; it != charsets.end(); ++it) { QCString encoding = (*it).latin1(); if (encoding == "locale") encoding = QCString(KGlobal::locale()->codecForEncoding()->mimeName()).lower(); if (encoding == "us-ascii") { bool ok; (void) KMMsgBase::toUsAscii(text, &ok); if (ok) return encoding; } else { QTextCodec *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; } //----------------------------------------------------------------------------- QCString KMMsgBase::decodeQuotedPrintable(const QCString& aStr) { QCString bStr = aStr; if (aStr.isNull()) bStr = ""; DwString dwsrc(bStr.data()); DwString dwdest; DwDecodeQuotedPrintable(dwsrc, dwdest); return dwdest.c_str(); } //----------------------------------------------------------------------------- QCString KMMsgBase::encodeQuotedPrintable(const QCString& aStr) { QCString bStr = aStr; if (aStr.isNull()) bStr = ""; DwString dwsrc(bStr.data(), bStr.length()); DwString dwdest; QCString result; DwEncodeQuotedPrintable(dwsrc, dwdest); result = dwdest.c_str(); return result; } //----------------------------------------------------------------------------- QCString KMMsgBase::decodeBase64(const QCString& aStr) { QCString bStr = aStr; if (aStr.isNull()) bStr = ""; while (bStr.length() < 16) bStr += "="; DwString dwsrc(bStr.data(), bStr.length()); DwString dwdest; QCString result; DwDecodeBase64(dwsrc, dwdest); result = dwdest.c_str(); return result; } //----------------------------------------------------------------------------- QCString KMMsgBase::encodeBase64(const QCString& aStr) { QCString bStr = aStr; if (aStr.isNull()) bStr = ""; DwString dwsrc(bStr.data(), bStr.length()); DwString dwdest; QCString result; DwEncodeBase64(dwsrc, dwdest); result = dwdest.c_str(); return result; } //----------------------------------------------------------------------------- unsigned long KMMsgBase::getMsgSerNum() const { unsigned long msn = 0; if (mParent) { int index = mParent->find((const KMMsgBasePtr)this); msn = kernel->msgDict()->getMsgSerNum(mParent, index); } return msn; } //----------------------------------------------------------------------------- void swapEndian(QString &str) { ushort us; uint len = str.length(); for (uint i = 0; i < len; i++) { us = str[i].unicode(); str[i] = QChar(((us & 0xFF) << 8) + ((us & 0xFF00) >> 8)); } } //----------------------------------------------------------------------------- static int g_chunk_length = 0, g_chunk_offset=0; static uchar *g_chunk = NULL; #define COPY_DATA(x, length) do { \ if(g_chunk_offset + ((int)length) > g_chunk_length) {\ g_chunk_offset = g_chunk_length; \ kdDebug(5006) << "This should never happen.. " << __FILE__ << ":" << __LINE__ << endl; \ memset(x, length, '\0'); \ } else { \ memcpy(x, g_chunk+g_chunk_offset, length); \ g_chunk_offset += length; \ } } while(0) #define COPY_HEADER_TYPE(x) Q_ASSERT(sizeof(x) == sizeof(MsgPartType)); COPY_DATA(&x, sizeof(x)) #define COPY_HEADER_LEN(x) Q_ASSERT(sizeof(x) == sizeof(short)); COPY_DATA(&x, sizeof(x)); //----------------------------------------------------------------------------- QString KMMsgBase::getStringPart(MsgPartType t) const { QString ret(""); g_chunk_offset = 0; bool using_mmap = FALSE; if (mParent->indexStreamBasePtr()) { if (g_chunk) free(g_chunk); using_mmap = TRUE; g_chunk = mParent->indexStreamBasePtr() + mIndexOffset; g_chunk_length = mIndexLength; } else { if(!mParent->mIndexStream) return ret; if (g_chunk_length < mIndexLength) g_chunk = (uchar *)realloc(g_chunk, g_chunk_length = mIndexLength); int first_off=ftell(mParent->mIndexStream); fseek(mParent->mIndexStream, mIndexOffset, SEEK_SET); fread( g_chunk, mIndexLength, 1, mParent->mIndexStream); fseek(mParent->mIndexStream, first_off, SEEK_SET); } MsgPartType type; short l; while(g_chunk_offset < mIndexLength) { COPY_HEADER_TYPE(type); COPY_HEADER_LEN(l); if(g_chunk_offset + l > mIndexLength) { kdDebug(5006) << "This should never happen.. " << __FILE__ << ":" << __LINE__ << endl; break; } if(type == t) { 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 = NULL; } swapEndian(ret); return ret; } //----------------------------------------------------------------------------- unsigned long KMMsgBase::getLongPart(MsgPartType t) const { unsigned long ret = 0; g_chunk_offset = 0; bool using_mmap = FALSE; if (mParent->indexStreamBasePtr()) { if (g_chunk) free(g_chunk); using_mmap = TRUE; g_chunk = mParent->indexStreamBasePtr() + mIndexOffset; g_chunk_length = mIndexLength; } else { if (!mParent->mIndexStream) return ret; assert(mIndexLength >= 0); if (g_chunk_length < mIndexLength) g_chunk = (uchar *)realloc(g_chunk, g_chunk_length = mIndexLength); int first_off=ftell(mParent->mIndexStream); fseek(mParent->mIndexStream, mIndexOffset, SEEK_SET); fread( g_chunk, mIndexLength, 1, mParent->mIndexStream); fseek(mParent->mIndexStream, first_off, SEEK_SET); } MsgPartType type; short l; while (g_chunk_offset < mIndexLength) { COPY_HEADER_TYPE(type); COPY_HEADER_LEN(l); if (g_chunk_offset + l > mIndexLength) { kdDebug(5006) << "This should never happen.. " << __FILE__ << ":" << __LINE__ << endl; break; } if(type == t) { Q_ASSERT(l == sizeof(unsigned long)); COPY_DATA(&ret, sizeof(ret)); break; } g_chunk_offset += l; } if(using_mmap) { g_chunk_length = 0; g_chunk = NULL; } return ret; } #undef COPY_DATA //----------------------------------------------------------------------------- const uchar *KMMsgBase::asIndexString(int &length) const { unsigned int csize = 256; static uchar *ret = NULL; //different static buffer here for we may use the other buffer in the functions below if(!ret) ret = (uchar *)malloc(csize); length = 0; #define STORE_DATA_LEN(type, x, len) 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)); \ MsgPartType t = type; memcpy(ret+length, &t, sizeof(MsgPartType)); \ short l = len2; memcpy(ret+length+sizeof(MsgPartType), &l, sizeof(short)); \ memcpy(ret+length+sizeof(short)+sizeof(MsgPartType), x, len2); \ length += len2 + sizeof(short) + sizeof(MsgPartType); \ } while(0) #define STORE_DATA(type, x) STORE_DATA_LEN(type, &x, sizeof(x)) unsigned long tmp; QString tmp_str; //these is at the beginning because it is queried quite often tmp_str = msgIdMD5().stripWhiteSpace(); swapEndian(tmp_str); STORE_DATA_LEN(MsgIdMD5Part, tmp_str.unicode(), tmp_str.length() * 2); tmp = status(); STORE_DATA(MsgStatusPart, tmp); //these are completely arbitrary order tmp_str = fromStrip().stripWhiteSpace(); swapEndian(tmp_str); STORE_DATA_LEN(MsgFromPart, tmp_str.unicode(), tmp_str.length() * 2); tmp_str = subject().stripWhiteSpace(); swapEndian(tmp_str); STORE_DATA_LEN(MsgSubjectPart, tmp_str.unicode(), tmp_str.length() * 2); tmp_str = toStrip().stripWhiteSpace(); swapEndian(tmp_str); STORE_DATA_LEN(MsgToPart, tmp_str.unicode(), tmp_str.length() * 2); tmp_str = replyToIdMD5().stripWhiteSpace(); swapEndian(tmp_str); STORE_DATA_LEN(MsgReplyToIdMD5Part, tmp_str.unicode(), tmp_str.length() * 2); tmp_str = xmark().stripWhiteSpace(); swapEndian(tmp_str); STORE_DATA_LEN(MsgXMarkPart, tmp_str.unicode(), tmp_str.length() * 2); tmp_str = fileName().stripWhiteSpace(); swapEndian(tmp_str); STORE_DATA_LEN(MsgFilePart, tmp_str.unicode(), tmp_str.length() * 2); tmp = msgSize(); STORE_DATA(MsgSizePart, tmp); tmp = folderOffset(); STORE_DATA(MsgOffsetPart, tmp); tmp = date(); STORE_DATA(MsgDatePart, tmp); #undef STORE_DATA_LEN return ret; } bool KMMsgBase::syncIndexString() const { if(!dirty()) return TRUE; int len; const uchar *buffer = asIndexString(len); if (len == mIndexLength) { Q_ASSERT(mParent->mIndexStream); fseek(mParent->mIndexStream, mIndexOffset, SEEK_SET); fwrite( buffer, len, 1, mParent->mIndexStream); return TRUE; } return FALSE; }