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.
tdepim/libkmime/kmime_util.cpp

807 lines
19 KiB

/*
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 <config.h>
#endif
#include "kmime_util.h"
#include <kmdcodec.h> // for KCodec::{quotedPrintableDe,base64{En,De}}code
#include <kglobal.h>
#include <klocale.h>
#include <kcharsets.h>
#include <kdeversion.h>
#if KDE_IS_VERSION( 3, 1, 90 )
#include <kcalendarsystem.h>
#endif
#include <qtextcodec.h>
#include <qstrlist.h> // for QStrIList
#include <qregexp.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h> // for time()
#include <unistd.h> // for getpid()
using namespace KMime;
namespace KMime {
QStrIList c_harsetCache;
QStrIList l_anguageCache;
const char* cachedCharset(const QCString &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 QCString &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 QString &s)
{
uint sLength = s.length();
for (uint i=0; i<sLength; i++)
if (s.at(i).latin1()<=0) // c==0: non-latin1, c<0: non-us-ascii
return false;
return true;
}
// "(),.:;<>@[\]
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
QString decodeRFC2047String(const QCString &src, const char **usedCS,
const QCString &defaultCS, bool forceCS)
{
QCString result, str;
QCString declaredCS;
char *pos, *dest, *beg, *end, *mid, *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 && (*pos!='?'&&(ispunct(*pos)||isalnum(*pos))); i++) {
declaredCS+=(*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) {
// cut all linear-white space between two encoded words
if (onlySpacesSinceLastWord)
dest=endOfLastEncWord;
if (mid < pos) {
str = QCString(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 QTextCodec
QTextCodec *codec=0;
bool ok=true;
if (forceCS || declaredCS.isEmpty()) {
codec=KGlobal::charsets()->codecForName(defaultCS);
(*usedCS)=cachedCharset(defaultCS);
}
else {
codec=KGlobal::charsets()->codecForName(declaredCS, ok);
if(!ok) { //no suitable codec found => use default charset
codec=KGlobal::charsets()->codecForName(defaultCS);
(*usedCS)=cachedCharset(defaultCS);
}
else
(*usedCS)=cachedCharset(declaredCS);
}
return codec->toUnicode(result.data(), result.length());
}
QString decodeRFC2047String(const QCString &src)
{
const char *usedCS;
return decodeRFC2047String(src, &usedCS, "utf-8", false);
}
QCString encodeRFC2047String(const QString &src, const char *charset,
bool addressHeader, bool allow8BitHeaders)
{
QCString encoded8Bit, result, usedCS;
unsigned int start=0,end=0;
bool nonAscii=false, ok=true, useQEncoding=false;
QTextCodec *codec=0;
usedCS=charset;
codec=KGlobal::charsets()->codecForName(usedCS, ok);
if(!ok) {
//no codec available => try local8Bit and hope the best ;-)
usedCS=KGlobal::locale()->encoding();
codec=KGlobal::charsets()->codecForName(usedCS, ok);
}
if (usedCS.find("8859-")>=0) // use "B"-Encoding for non iso-8859-x charsets
useQEncoding=true;
encoded8Bit=codec->fromUnicode(src);
if(allow8BitHeaders)
return encoded8Bit;
uint encoded8BitLength = encoded8Bit.length();
for (unsigned int i=0; i<encoded8BitLength; i++) {
if (encoded8Bit[i]==' ') // encoding starts at word boundaries
start = i+1;
// encode escape character, for japanese encodings...
if (((signed char)encoded8Bit[i]<0) || (encoded8Bit[i] == '\033') ||
(addressHeader && (strchr("\"()<>@,.;:\\[]=",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.length())&&(encoded8Bit[end]!=' ')) // we encode complete words
end++;
for (unsigned int x=end;x<encoded8Bit.length();x++)
if (((signed char)encoded8Bit[x]<0) || (encoded8Bit[x] == '\033') ||
(addressHeader && (strchr("\"()<>@,.;:\\[]=",encoded8Bit[x])!=0))) {
end = encoded8Bit.length(); // we found another non-ascii word
while ((end<encoded8Bit.length())&&(encoded8Bit[end]!=' ')) // we encode complete words
end++;
}
result = encoded8Bit.left(start)+"=?"+usedCS;
if (useQEncoding) {
result += "?Q?";
char c,hexcode; // implementation of the "Q"-encoding described in RFC 2047
for (unsigned int i=start;i<end;i++) {
c = encoded8Bit[i];
if (c == ' ') // make the result readable with not MIME-capable readers
result+='_';
else
if (((c>='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;
}
QCString uniqueString()
{
static char chars[] = "0123456789abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
time_t now;
QCString 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;
}
QCString multiPartBoundary()
{
QCString ret;
ret="nextPart"+uniqueString();
return ret;
}
QCString extractHeader(const QCString &src, const char *name)
{
QCString n=QCString(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,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(QRegExp("\\s*\\n\\s*")," "));
}
else {
return QCString(0); //header not found
}
}
QCString CRLFtoLF(const QCString &s)
{
QCString ret=s.copy();
ret.replace(QRegExp("\\r\\n"), "\n");
return ret;
}
QCString CRLFtoLF(const char *s)
{
QCString ret=s;
ret.replace(QRegExp("\\r\\n"), "\n");
return ret;
}
QCString LFtoCRLF(const QCString &s)
{
QCString ret=s.copy();
ret.replace(QRegExp("\\n"), "\r\n");
return ret;
}
void removeQuots(QCString &str)
{
bool inQuote=false;
for (int i=0; i < (int)str.length(); i++) {
if (str[i] == '"') {
str.remove(i,1);
i--;
inQuote = !inQuote;
} else {
if (inQuote && (str[i] == '\\'))
str.remove(i,1);
}
}
}
void removeQuots(QString &str)
{
bool inQuote=false;
for (int i=0; i < (int)str.length(); i++) {
if (str[i] == '"') {
str.remove(i,1);
i--;
inQuote = !inQuote;
} else {
if (inQuote && (str[i] == '\\'))
str.remove(i,1);
}
}
}
void addQuotes(QCString &str, bool forceQuotes)
{
bool needsQuotes=false;
for (unsigned int i=0; i < str.length(); i++) {
if (strchr("()<>@,.;:[]=\\\"",str[i])!=0)
needsQuotes = true;
if (str[i]=='\\' || str[i]=='\"') {
str.insert(i, '\\');
i++;
}
}
if (needsQuotes || forceQuotes) {
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;
}
QString
DateFormatter::dateString( time_t otime , const QString& 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 QString::null;
}
QString
DateFormatter::dateString(const QDateTime& dtime, const QString& lang,
bool shortFormat, bool includeSecs ) const
{
return DateFormatter::dateString( qdateToTimeT(dtime), lang, shortFormat, includeSecs );
}
QCString
DateFormatter::rfc2822(time_t otime) const
{
QDateTime tmp;
QCString ret;
tmp.setTime_t(otime);
ret = tmp.toString("ddd, dd MMM yyyy hh:mm:ss ").latin1();
ret += zone(otime);
return ret;
}
QString
DateFormatter::custom(time_t t) const
{
if ( mCustomFormat.isEmpty() )
return QString::null;
int z = mCustomFormat.find("Z");
QDateTime d;
QString 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 QString& format)
{
mCustomFormat = format;
mFormat = Custom;
}
QString
DateFormatter::getCustomFormat() const
{
return mCustomFormat;
}
QCString
DateFormatter::zone(time_t otime) const
{
QCString 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
QDateTime d1 = QDateTime::fromString( asctime(gmtime(&otime)) );
QDateTime d2 = QDateTime::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 QDateTime& dt) const
{
QDateTime epoch( QDate(1970, 1,1), QTime(00,00,00) );
time_t otime;
time( &otime );
QDateTime d1 = QDateTime::fromString( asctime(gmtime(&otime)) );
QDateTime d2 = QDateTime::fromString( asctime(localtime(&otime)) );
time_t drf = epoch.secsTo( dt ) - d1.secsTo( d2 );
return drf;
}
QString
DateFormatter::fancy(time_t otime) const
{
KLocale *locale = KGlobal::locale();
if ( otime <= 0 )
return i18n( "unknown" );
if ( !mCurrentTime ) {
time( &mCurrentTime );
mDate.setTime_t( mCurrentTime );
}
QDateTime 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 ) {
QDateTime 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 ) {
QDateTime 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 );
}
QString
DateFormatter::localized(time_t otime, bool shortFormat, bool includeSecs,
const QString& localeLanguage ) const
{
QDateTime tmp;
QString ret;
KLocale *locale = KGlobal::locale();
tmp.setTime_t( otime );
if ( !localeLanguage.isEmpty() ) {
locale=new KLocale(localeLanguage);
locale->setLanguage(localeLanguage);
locale->setCountry(localeLanguage);
ret = locale->formatDateTime( tmp, shortFormat, includeSecs );
delete locale;
} else {
ret = locale->formatDateTime( tmp, shortFormat, includeSecs );
}
return ret;
}
QString
DateFormatter::cTime(time_t otime) const
{
return QString::fromLatin1( ctime( &otime ) ).stripWhiteSpace() ;
}
QString
DateFormatter::isoDate(time_t otime) const
{
char cstr[64];
strftime( cstr, 63, "%Y-%m-%d %H:%M:%S", localtime(&otime) );
return QString( cstr );
}
void
DateFormatter::reset()
{
mCurrentTime = 0;
}
QString
DateFormatter::formatDate(DateFormatter::FormatType t, time_t otime,
const QString& data, bool shortFormat, bool includeSecs )
{
DateFormatter f( t );
if ( t == DateFormatter::Custom ) {
f.setCustomFormat( data );
}
return f.dateString( otime, data, shortFormat, includeSecs );
}
QString
DateFormatter::formatCurrentDate( DateFormatter::FormatType t, const QString& data,
bool shortFormat, bool includeSecs )
{
DateFormatter f( t );
if ( t == DateFormatter::Custom ) {
f.setCustomFormat( data );
}
return f.dateString( time(0), data, shortFormat, includeSecs );
}
QCString
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