Logo Search packages:      
Sourcecode: mailody version File versions  Download package

kmime_util.cpp

/*
    kmime_util.cpp

    KMime, the KDE internet mail/usenet news message library.
    Copyright (c) 2001 the KMime authors.
    See file AUTHORS for details

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "kmime_util.h"
#include "kmime_codecs.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 = decodeQP(str);
          }
          else
          {
            str = KCodecs::base64Decode(str);
          }
          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());
}

QCString decodeQP(const QCString& input)
{
    KMime::Codec * codec = KMime::Codec::codecForName( "quoted-printable" );

    QByteArray in(input.length());
    memcpy(in.data(), input.data(), input.length());

    QByteArray out = codec->decode(in, false);

    QCString output(out.size()+1);
    memcpy(output.data(), out.data(), out.size());

    return output;
}

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;
00503 DateFormatter::DateFormatter(FormatType fType)
  : mFormat( fType ), mCurrentTime( 0 )
{

}

DateFormatter::~DateFormatter()
{/*empty*/}

DateFormatter::FormatType
00513 DateFormatter::getFormat() const
{
  return mFormat;
}

void
00519 DateFormatter::setFormat( FormatType t )
{
  mFormat = t;
}

QString
00525 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
00549 DateFormatter::dateString(const QDateTime& dtime, const QString& lang,
                   bool shortFormat, bool includeSecs ) const
{
  return DateFormatter::dateString( qdateToTimeT(dtime), lang, shortFormat, includeSecs );
}

QCString
00556 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
00570 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
00590 DateFormatter::setCustomFormat(const QString& format)
{
  mCustomFormat = format;
  mFormat = Custom;
}

QString
DateFormatter::getCustomFormat() const
{
  return mCustomFormat;
}


QCString
00604 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
00677 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
00729 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
00753 DateFormatter::cTime(time_t otime) const
{
  return QString::fromLatin1( ctime(  &otime ) ).stripWhiteSpace() ;
}

QString
00759 DateFormatter::isoDate(time_t otime) const
{
  char cstr[64];
  strftime( cstr, 63, "%Y-%m-%d %H:%M:%S", localtime(&otime) );
  return QString( cstr );
}


void
00768 DateFormatter::reset()
{
  mCurrentTime = 0;
}

QString
00774 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
00785 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
00796 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

Generated by  Doxygen 1.6.0   Back to index