// kmmsgbase.cpp #include #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 #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_BYTESWAP_H #include #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( 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; case KMMsgStatusHasInvitation: mStatus &= ~KMMsgStatusHasNoInvitation; mStatus |= KMMsgStatusHasInvitation; break; case KMMsgStatusHasNoInvitation: mStatus &= ~KMMsgStatusHasInvitation; mStatus |= KMMsgStatusHasNoInvitation; 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 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 // . 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(); const TQCString str = unfold( aStr ); if ( str.isEmpty() ) return TQString(); if ( str.find( "=?" ) < 0 ) { if ( !prefCharset.isEmpty() && kmkernel->isCodecAsciiCompatible( KMMsgBase::codecForName( prefCharset ) ) ) { if ( prefCharset == "us-ascii" ) { // isn`t this foolproof? return KMMsgBase::codecForName( "utf-8" )->toUnicode( str ); } else { return KMMsgBase::codecForName( prefCharset )->toUnicode( str ); } } else { if ( kmkernel->isCodecAsciiCompatible( KMMsgBase::codecForName( GlobalSettings::self()->fallbackCharacterEncoding().latin1() ) ) ) { return KMMsgBase::codecForName( GlobalSettings::self()-> fallbackCharacterEncoding().latin1() )->toUnicode( str ); } } // Not RFC2047 encoded, and codec not ascii-compatible -> interpret as ascii return TQString::fromAscii( 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; } //----------------------------------------------------------------------------- TQCString KMMsgBase::encodeRFC2231StringAutoDetectCharset( const TQString &str, const TQCString &defaultCharset ) { TQCString encoding = KMMsgBase::autoDetectCharset( defaultCharset, KMMessage::preferredCharsets(), str ); if ( encoding.isEmpty() ) encoding = "utf-8"; return KMMsgBase::encodeRFC2231String( str, encoding ); } //----------------------------------------------------------------------------- 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; } KMMsgInvitationState KMMsgBase::invitationState() const { KMMsgStatus st = status(); if (st & KMMsgStatusHasInvitation) return KMMsgHasInvitation; else if (st & KMMsgStatusHasNoInvitation) return KMMsgHasNoInvitation; else return KMMsgInvitationUnknown; } //----------------------------------------------------------------------------- static void swapEndian(TQString &str) { uint len = str.length(); str = TQDeepCopy(str); TQChar *unicode = const_cast( 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; TQ_UINT16 l; while(g_chunk_offset < mIndexLength) { TQ_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 TQStrings are written // in the style of TQt2 (MSB -> network ordered). // TQStrings in TQt3 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; TQ_UINT16 l; while (g_chunk_offset < mIndexLength) { TQ_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 TQ_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 TQ_UINT32 ret_1; TQ_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)); \ TQ_UINT32 t = (TQ_UINT32) type; memcpy(ret+length, &t, sizeof(t)); \ TQ_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(MsgFromStripPart, 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(MsgToStripPart, 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); tmp_str = from(); STORE_DATA_LEN( MsgFromPart, tmp_str.unicode(), tmp_str.length() * 2, true ); tmp_str = to(); STORE_DATA_LEN( MsgToPart, tmp_str.unicode(), tmp_str.length() * 2, true ); 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() ).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() ).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:" ); }