/* kmime_util.cpp KMime, the KDE internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US */ #ifdef HAVE_CONFIG_H #include #endif #include "kmime_util.h" #include // for KCodec::{quotedPrintableDe,base64{En,De}}code #include #include #include #include #if KDE_IS_VERSION( 3, 1, 90 ) #include #endif #include #include // for TQStrIList #include #include #include #include #include // for time() #include // for getpid() using namespace KMime; namespace KMime { TQStrIList c_harsetCache; TQStrIList l_anguageCache; const char* cachedCharset(const TQCString &name) { int idx=c_harsetCache.find(name.data()); if(idx>-1) return c_harsetCache.at(idx); c_harsetCache.append(name.upper().data()); //kdDebug() << "KNMimeBase::cachedCharset() number of cs " << c_harsetCache.count() << endl; return c_harsetCache.last(); } const char* cachedLanguage(const TQCString &name) { int idx=l_anguageCache.find(name.data()); if(idx>-1) return l_anguageCache.at(idx); l_anguageCache.append(name.upper().data()); //kdDebug() << "KNMimeBase::cachedCharset() number of cs " << c_harsetCache.count() << endl; return l_anguageCache.last(); } bool isUsAscii(const TQString &s) { uint sLength = s.length(); for (uint i=0; i@[\] const uchar specialsMap[16] = { 0x00, 0x00, 0x00, 0x00, // CTLs 0x20, 0xCA, 0x00, 0x3A, // SPACE ... '?' 0x80, 0x00, 0x00, 0x1C, // '@' ... '_' 0x00, 0x00, 0x00, 0x00 // '`' ... DEL }; // "(),:;<>@[\]/=? const uchar tSpecialsMap[16] = { 0x00, 0x00, 0x00, 0x00, // CTLs 0x20, 0xC9, 0x00, 0x3F, // SPACE ... '?' 0x80, 0x00, 0x00, 0x1C, // '@' ... '_' 0x00, 0x00, 0x00, 0x00 // '`' ... DEL }; // all except specials, CTLs, SPACE. const uchar aTextMap[16] = { 0x00, 0x00, 0x00, 0x00, 0x5F, 0x35, 0xFF, 0xC5, 0x7F, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFE }; // all except tspecials, CTLs, SPACE. const uchar tTextMap[16] = { 0x00, 0x00, 0x00, 0x00, 0x5F, 0x36, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFE }; // none except a-zA-Z0-9!*+-/ const uchar eTextMap[16] = { 0x00, 0x00, 0x00, 0x00, 0x40, 0x35, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xFF, 0xE0 }; #if defined(_AIX) && defined(truncate) #undef truncate #endif TQString decodeRFC2047String(const TQCString &src, const char **usedCS, const TQCString &defaultCS, bool forceCS) { TQCString result, str; TQCString declaredCS; const char *beg, *end, *mid, *pos=0; char *dest, *endOfLastEncWord=0; char encoding = '\0'; bool valid, onlySpacesSinceLastWord=false; const int maxLen=400; int i; if(src.find("=?") < 0) result = src.copy(); else { result.truncate(src.length()); for (pos=src.data(), dest=result.data(); *pos; pos++) { if (pos[0]!='=' || pos[1]!='?') { *dest++ = *pos; if (onlySpacesSinceLastWord) onlySpacesSinceLastWord = (pos[0]==' ' || pos[1]=='\t'); continue; } beg = pos+2; end = beg; valid = TRUE; // parse charset name declaredCS=""; 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) { // cut all linear-white space between two encoded words if (onlySpacesSinceLastWord) dest=endOfLastEncWord; if (mid < pos) { str = TQCString(mid, (int)(pos - mid + 1)); if (encoding == 'Q') { // decode quoted printable text for (i=str.length()-1; i>=0; i--) if (str[i]=='_') str[i]=' '; str = KCodecs::quotedPrintableDecode(str); } else { str = KCodecs::base64Decode(str); } if (!str.isNull()) { for (i=0; str[i]; i++) { *dest++ = str[i]; } } } endOfLastEncWord=dest; onlySpacesSinceLastWord=true; pos = end -1; } else { pos = beg - 2; *dest++ = *pos++; *dest++ = *pos; } } *dest = '\0'; } //find suitable TQTextCodec TQTextCodec *codec=0; bool ok=true; if (forceCS || declaredCS.isEmpty()) { codec=TDEGlobal::charsets()->codecForName(defaultCS); (*usedCS)=cachedCharset(defaultCS); } else { codec=TDEGlobal::charsets()->codecForName(declaredCS, ok); if(!ok) { //no suitable codec found => use default charset codec=TDEGlobal::charsets()->codecForName(defaultCS); (*usedCS)=cachedCharset(defaultCS); } else (*usedCS)=cachedCharset(declaredCS); } return codec->toUnicode(result.data(), result.length()); } TQString decodeRFC2047String(const TQCString &src) { const char *usedCS; return decodeRFC2047String(src, &usedCS, "utf-8", false); } TQCString encodeRFC2047String(const TQString &src, const char *charset, bool addressHeader, bool allow8BitHeaders) { TQCString encoded8Bit, result, usedCS; unsigned int start=0,end=0; bool nonAscii=false, ok=true, useTQEncoding=false; TQTextCodec *codec=0; usedCS=charset; codec=TDEGlobal::charsets()->codecForName(usedCS, ok); if(!ok) { //no codec available => try local8Bit and hope the best ;-) usedCS=TDEGlobal::locale()->encoding(); codec=TDEGlobal::charsets()->codecForName(usedCS, ok); } if (usedCS.find("8859-")>=0) // use "B"-Encoding for non iso-8859-x charsets useTQEncoding=true; encoded8Bit=codec->fromUnicode(src); if(allow8BitHeaders) return encoded8Bit; uint encoded8BitLength = encoded8Bit.length(); for (unsigned int i=0; i@,.;:\\[]=",encoded8Bit[i])!=0))) { end = start; // non us-ascii char found, now we determine where to stop encoding nonAscii=true; break; } } if (nonAscii) { while ((end@,.;:\\[]=",encoded8Bit[x])!=0))) { end = encoded8Bit.length(); // we found another non-ascii word while ((end='a')&&(c<='z'))|| // paranoid mode, we encode *all* special characters to avoid problems ((c>='A')&&(c<='Z'))|| // with "From" & "To" headers ((c>='0')&&(c<='9'))) result+=c; else { result += "="; // "stolen" from KMail ;-) hexcode = ((c & 0xF0) >> 4) + 48; if (hexcode >= 58) hexcode += 7; result += hexcode; hexcode = (c & 0x0F) + 48; if (hexcode >= 58) hexcode += 7; result += hexcode; } } } else { result += "?B?"+KCodecs::base64Encode(encoded8Bit.mid(start,end-start), false); } result +="?="; result += encoded8Bit.right(encoded8Bit.length()-end); } else result = encoded8Bit; return result; } TQCString uniqueString() { static char chars[] = "0123456789abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; time_t now; TQCString ret; char p[11]; int pos, ran; unsigned int timeval; p[10]='\0'; now=time(0); ran=1+(int) (1000.0*rand()/(RAND_MAX+1.0)); timeval=(now/ran)+getpid(); for(int i=0; i<10; i++){ pos=(int) (61.0*rand()/(RAND_MAX+1.0)); //kdDebug(5003) << pos << endl; p[i]=chars[pos]; } ret.sprintf("%d.%s", timeval, p); return ret; } TQCString multiPartBoundary() { TQCString ret; ret="nextPart"+uniqueString(); return ret; } TQCString extractHeader(const TQCString &src, const char *name) { TQCString n=TQCString(name)+":"; int pos1=-1, pos2=0, len=src.length()-1; bool folded(false); if (n.lower() == src.left(n.length()).lower()) { pos1 = 0; } else { n.prepend("\n"); pos1 = src.find(n.data(),0,false); } if (pos1>-1) { //there is a header with the given name pos1+=n.length(); //skip the name // skip the usual space after the colon if ( src.at( pos1 ) == ' ' ) ++pos1; pos2=pos1; if (src[pos2]!='\n') { // check if the header is not empty while(1) { pos2=src.find("\n", pos2+1); if(pos2==-1 || pos2==len || ( src[pos2+1]!=' ' && src[pos2+1]!='\t') ) //break if we reach the end of the string, honor folded lines break; else folded = true; } } if(pos2<0) pos2=len+1; //take the rest of the string if (!folded) return src.mid(pos1, pos2-pos1); else return (src.mid(pos1, pos2-pos1).replace(TQRegExp("\\s*\\n\\s*")," ")); } else { return TQCString(0); //header not found } } TQCString CRLFtoLF(const TQCString &s) { TQCString ret=s.copy(); ret.replace(TQRegExp("\\r\\n"), "\n"); return ret; } TQCString CRLFtoLF(const char *s) { TQCString ret=s; ret.replace(TQRegExp("\\r\\n"), "\n"); return ret; } TQCString LFtoCRLF(const TQCString &s) { TQCString ret=s.copy(); ret.replace(TQRegExp("\\n"), "\r\n"); return ret; } void removeQuots(TQCString &str) { // Removes any quote or backslash caracter str.replace(TQRegExp("[\\\"]"), ""); } void removeQuots(TQString &str) { // Removes any quote or backslash caracter str.replace(TQRegExp("[\\\"]"), ""); } void addQuotes(TQCString &str, bool forceQuotes) { if ( forceQuotes || TQString(str).contains( TQRegExp( TQString( "\"|\\\\|=|\\]|\\[|:|;|,|\\.|,|@|<|>|\\)|\\(" ) ) ) ) { // Adds a backslash in front of any existing quote or backslash caracter str.replace(TQRegExp("([\\\"])"), "\\\\1"); // Adds quote at beginning and end of thestring str.insert(0,'"'); str.append("\""); } } int DateFormatter::mDaylight = -1; DateFormatter::DateFormatter(FormatType fType) : mFormat( fType ), mCurrentTime( 0 ) { } DateFormatter::~DateFormatter() {/*empty*/} DateFormatter::FormatType DateFormatter::getFormat() const { return mFormat; } void DateFormatter::setFormat( FormatType t ) { mFormat = t; } TQString DateFormatter::dateString( time_t otime , const TQString& lang , bool shortFormat, bool includeSecs ) const { switch ( mFormat ) { case Fancy: return fancy( otime ); break; case Localized: return localized( otime, shortFormat, includeSecs, lang ); break; case CTime: return cTime( otime ); break; case Iso: return isoDate( otime ); break; case Custom: return custom( otime ); break; } return TQString(); } TQString DateFormatter::dateString(const TQDateTime& dtime, const TQString& lang, bool shortFormat, bool includeSecs ) const { return DateFormatter::dateString( qdateToTimeT(dtime), lang, shortFormat, includeSecs ); } TQCString DateFormatter::rfc2822(time_t otime) const { TQDateTime tmp; TQCString ret; tmp.setTime_t(otime); ret = tmp.toString("ddd, dd MMM yyyy hh:mm:ss ").latin1(); ret += zone(otime); return ret; } TQString DateFormatter::custom(time_t t) const { if ( mCustomFormat.isEmpty() ) return TQString(); int z = mCustomFormat.find("Z"); TQDateTime d; TQString ret = mCustomFormat; d.setTime_t(t); if ( z != -1 ) { ret.replace(z,1,zone(t)); } ret = d.toString(ret); return ret; } void DateFormatter::setCustomFormat(const TQString& format) { mCustomFormat = format; mFormat = Custom; } TQString DateFormatter::getCustomFormat() const { return mCustomFormat; } TQCString DateFormatter::zone(time_t otime) const { TQCString ret; #if defined(HAVE_TIMEZONE) || defined(HAVE_TM_GMTOFF) struct tm *local = localtime( &otime ); #endif #if defined(HAVE_TIMEZONE) //hmm, could make hours & mins static int secs = abs(timezone); int neg = (timezone>0)?1:0; int hours = secs/3600; int mins = (secs - hours*3600)/60; // adjust to daylight if ( local->tm_isdst > 0 ) { mDaylight = 1; if ( neg ) --hours; else ++hours; } else mDaylight = 0; ret.sprintf("%c%.2d%.2d",(neg)?'-':'+', hours, mins); #elif defined(HAVE_TM_GMTOFF) int secs = abs( local->tm_gmtoff ); int neg = (local->tm_gmtoff<0)?1:0; //no, I don't know why it's backwards :o int hours = secs/3600; int mins = (secs - hours*3600)/60; if ( local->tm_isdst > 0 ) mDaylight = 1; else mDaylight = 0; ret.sprintf("%c%.2d%.2d",(neg)?'-':'+', hours, mins); #else TQDateTime d1 = TQDateTime::fromString( asctime(gmtime(&otime)) ); TQDateTime d2 = TQDateTime::fromString( asctime(localtime(&otime)) ); int secs = d1.secsTo(d2); int neg = (secs<0)?1:0; secs = abs(secs); int hours = secs/3600; int mins = (secs - hours*3600)/60; // daylight should be already taken care of here ret.sprintf("%c%.2d%.2d",(neg)?'-':'+', hours, mins); #endif /* HAVE_TIMEZONE */ return ret; } time_t DateFormatter::qdateToTimeT(const TQDateTime& dt) const { TQDateTime epoch( TQDate(1970, 1,1), TQTime(00,00,00) ); time_t otime; time( &otime ); TQDateTime d1 = TQDateTime::fromString( asctime(gmtime(&otime)) ); TQDateTime d2 = TQDateTime::fromString( asctime(localtime(&otime)) ); time_t drf = epoch.secsTo( dt ) - d1.secsTo( d2 ); return drf; } TQString DateFormatter::fancy(time_t otime) const { TDELocale *locale = TDEGlobal::locale(); if ( otime <= 0 ) return i18n( "unknown" ); if ( !mCurrentTime ) { time( &mCurrentTime ); mDate.setTime_t( mCurrentTime ); } TQDateTime old; old.setTime_t( otime ); // not more than an hour in the future if ( mCurrentTime + 60 * 60 >= otime ) { time_t diff = mCurrentTime - otime; if ( diff < 24 * 60 * 60 ) { if ( old.date().year() == mDate.date().year() && old.date().dayOfYear() == mDate.date().dayOfYear() ) return i18n( "Today %1" ).arg( locale-> formatTime( old.time(), true ) ); } if ( diff < 2 * 24 * 60 * 60 ) { TQDateTime yesterday( mDate.addDays( -1 ) ); if ( old.date().year() == yesterday.date().year() && old.date().dayOfYear() == yesterday.date().dayOfYear() ) return i18n( "Yesterday %1" ).arg( locale-> formatTime( old.time(), true) ); } for ( int i = 3; i < 7; i++ ) if ( diff < i * 24 * 60 * 60 ) { TQDateTime weekday( mDate.addDays( -i + 1 ) ); if ( old.date().year() == weekday.date().year() && old.date().dayOfYear() == weekday.date().dayOfYear() ) return i18n( "1. weekday, 2. time", "%1 %2" ). #if KDE_IS_VERSION( 3, 1, 90 ) arg( locale->calendar()->weekDayName( old.date() ) ). #else arg( locale->weekDayName( old.date().dayOfWeek() ) ). #endif arg( locale->formatTime( old.time(), true) ); } } return locale->formatDateTime( old ); } TQString DateFormatter::localized(time_t otime, bool shortFormat, bool includeSecs, const TQString& localeLanguage ) const { TQDateTime tmp; TQString ret; TDELocale *locale = TDEGlobal::locale(); tmp.setTime_t( otime ); if ( !localeLanguage.isEmpty() ) { locale=new TDELocale(localeLanguage); locale->setLanguage(localeLanguage); locale->setCountry(localeLanguage); ret = locale->formatDateTime( tmp, shortFormat, includeSecs ); delete locale; } else { ret = locale->formatDateTime( tmp, shortFormat, includeSecs ); } return ret; } TQString DateFormatter::cTime(time_t otime) const { return TQString::fromLatin1( ctime( &otime ) ).stripWhiteSpace() ; } TQString DateFormatter::isoDate(time_t otime) const { char cstr[64]; strftime( cstr, 63, "%Y-%m-%d %H:%M:%S", localtime(&otime) ); return TQString( cstr ); } void DateFormatter::reset() { mCurrentTime = 0; } TQString DateFormatter::formatDate(DateFormatter::FormatType t, time_t otime, const TQString& data, bool shortFormat, bool includeSecs ) { DateFormatter f( t ); if ( t == DateFormatter::Custom ) { f.setCustomFormat( data ); } return f.dateString( otime, data, shortFormat, includeSecs ); } TQString DateFormatter::formatCurrentDate( DateFormatter::FormatType t, const TQString& data, bool shortFormat, bool includeSecs ) { DateFormatter f( t ); if ( t == DateFormatter::Custom ) { f.setCustomFormat( data ); } return f.dateString( time(0), data, shortFormat, includeSecs ); } TQCString DateFormatter::rfc2822FormatDate( time_t t ) { DateFormatter f; return f.rfc2822( t ); } bool DateFormatter::isDaylight() { if ( mDaylight == -1 ) { time_t ntime = time( 0 ); struct tm *local = localtime( &ntime ); if ( local->tm_isdst > 0 ) { mDaylight = 1; return true; } else { mDaylight = 0; return false; } } else if ( mDaylight != 0 ) return true; else return false; } } // namespace KMime