• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • Sitemap
  • Contact Us
 

KMIME Library

kmime_util.cpp

00001 /*
00002   kmime_util.cpp
00003 
00004   KMime, the KDE internet mail/usenet news message library.
00005   Copyright (c) 2001 the KMime authors.
00006   See file AUTHORS for details
00007 
00008   This library is free software; you can redistribute it and/or
00009   modify it under the terms of the GNU Library General Public
00010   License as published by the Free Software Foundation; either
00011   version 2 of the License, or (at your option) any later version.
00012 
00013   This library is distributed in the hope that it will be useful,
00014   but WITHOUT ANY WARRANTY; without even the implied warranty of
00015   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016   Library General Public License for more details.
00017 
00018   You should have received a copy of the GNU Library General Public License
00019   along with this library; see the file COPYING.LIB.  If not, write to
00020   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00021   Boston, MA 02110-1301, USA.
00022 */
00023 
00024 #include "kmime_util.h"
00025 #include "kmime_util_p.h"
00026 #include "kmime_header_parsing.h"
00027 
00028 #include <config-kmime.h>
00029 #include <kdefakes.h> // for strcasestr
00030 #include <kglobal.h>
00031 #include <klocale.h>
00032 #include <kcharsets.h>
00033 #include <kcodecs.h>
00034 
00035 #include <QtCore/QList>
00036 #include <QtCore/QString>
00037 #include <QtCore/QTextCodec>
00038 
00039 #include <ctype.h>
00040 #include <time.h>
00041 #include <stdlib.h>
00042 #include <unistd.h>
00043 
00044 using namespace KMime;
00045 
00046 namespace KMime {
00047 
00048 QList<QByteArray> c_harsetCache;
00049 QList<QByteArray> l_anguageCache;
00050 
00051 QByteArray cachedCharset( const QByteArray &name )
00052 {
00053   foreach ( const QByteArray& charset, c_harsetCache ) {
00054     if ( qstricmp( name.data(), charset.data() ) == 0 ) {
00055       return charset;
00056     }
00057   }
00058 
00059   c_harsetCache.append( name.toUpper() );
00060   //kDebug(5320) << "KNMimeBase::cachedCharset() number of cs" << c_harsetCache.count();
00061   return c_harsetCache.last();
00062 }
00063 
00064 QByteArray cachedLanguage( const QByteArray &name )
00065 {
00066   foreach ( const QByteArray& language, l_anguageCache ) {
00067     if ( qstricmp( name.data(), language.data() ) == 0 ) {
00068       return language;
00069     }
00070   }
00071 
00072   l_anguageCache.append( name.toUpper() );
00073   //kDebug(5320) << "KNMimeBase::cachedCharset() number of cs" << c_harsetCache.count();
00074   return l_anguageCache.last();
00075 }
00076 
00077 bool isUsAscii( const QString &s )
00078 {
00079   uint sLength = s.length();
00080   for ( uint i=0; i<sLength; i++ ) {
00081     if ( s.at( i ).toLatin1() <= 0 ) { // c==0: non-latin1, c<0: non-us-ascii
00082       return false;
00083     }
00084   }
00085   return true;
00086 }
00087 
00088 // "(),.:;<>@[\]
00089 const uchar specialsMap[16] = {
00090   0x00, 0x00, 0x00, 0x00, // CTLs
00091   0x20, 0xCA, 0x00, 0x3A, // SPACE ... '?'
00092   0x80, 0x00, 0x00, 0x1C, // '@' ... '_'
00093   0x00, 0x00, 0x00, 0x00  // '`' ... DEL
00094 };
00095 
00096 // "(),:;<>@[\]/=?
00097 const uchar tSpecialsMap[16] = {
00098   0x00, 0x00, 0x00, 0x00, // CTLs
00099   0x20, 0xC9, 0x00, 0x3F, // SPACE ... '?'
00100   0x80, 0x00, 0x00, 0x1C, // '@' ... '_'
00101   0x00, 0x00, 0x00, 0x00  // '`' ... DEL
00102 };
00103 
00104 // all except specials, CTLs, SPACE.
00105 const uchar aTextMap[16] = {
00106   0x00, 0x00, 0x00, 0x00,
00107   0x5F, 0x35, 0xFF, 0xC5,
00108   0x7F, 0xFF, 0xFF, 0xE3,
00109   0xFF, 0xFF, 0xFF, 0xFE
00110 };
00111 
00112 // all except tspecials, CTLs, SPACE.
00113 const uchar tTextMap[16] = {
00114   0x00, 0x00, 0x00, 0x00,
00115   0x5F, 0x36, 0xFF, 0xC0,
00116   0x7F, 0xFF, 0xFF, 0xE3,
00117   0xFF, 0xFF, 0xFF, 0xFE
00118 };
00119 
00120 // none except a-zA-Z0-9!*+-/
00121 const uchar eTextMap[16] = {
00122   0x00, 0x00, 0x00, 0x00,
00123   0x40, 0x35, 0xFF, 0xC0,
00124   0x7F, 0xFF, 0xFF, 0xE0,
00125   0x7F, 0xFF, 0xFF, 0xE0
00126 };
00127 
00128 QString decodeRFC2047String( const QByteArray &src, QByteArray &usedCS,
00129                              const QByteArray &defaultCS, bool forceCS )
00130 {
00131   QByteArray result;
00132   QByteArray spaceBuffer;
00133   const char *scursor = src.constData();
00134   const char *send = scursor + src.length();
00135   bool onlySpacesSinceLastWord = false;
00136 
00137   while ( scursor != send ) {
00138      // space
00139     if ( isspace( *scursor ) && onlySpacesSinceLastWord ) {
00140       spaceBuffer += *scursor++;
00141       continue;
00142     }
00143 
00144     // possible start of an encoded word
00145     if ( *scursor == '=' ) {
00146       QByteArray language;
00147       QString decoded;
00148       ++scursor;
00149       const char *start = scursor;
00150       if ( HeaderParsing::parseEncodedWord( scursor, send, decoded, language, usedCS, defaultCS, forceCS ) ) {
00151         result += decoded.toUtf8();
00152         onlySpacesSinceLastWord = true;
00153         spaceBuffer.clear();
00154       } else {
00155         if ( onlySpacesSinceLastWord ) {
00156           result += spaceBuffer;
00157           onlySpacesSinceLastWord = false;
00158         }
00159         result += '=';
00160         scursor = start; // reset cursor after parsing failure
00161       }
00162       continue;
00163     } else {
00164       // unencoded data
00165       if ( onlySpacesSinceLastWord ) {
00166         result += spaceBuffer;
00167         onlySpacesSinceLastWord = false;
00168       }
00169       result += *scursor;
00170       ++scursor;
00171     }
00172   }
00173 
00174   return QString::fromUtf8(result);
00175 }
00176 
00177 QString decodeRFC2047String( const QByteArray &src )
00178 {
00179   QByteArray usedCS;
00180   return decodeRFC2047String( src, usedCS, "utf-8", false );
00181 }
00182 
00183 QByteArray encodeRFC2047String( const QString &src, const QByteArray &charset,
00184                                 bool addressHeader, bool allow8BitHeaders )
00185 {
00186   QByteArray encoded8Bit, result, usedCS;
00187   int start=0, end=0;
00188   bool nonAscii=false, ok=true, useQEncoding=false;
00189   QTextCodec *codec=0;
00190 
00191   usedCS = charset;
00192   codec = KGlobal::charsets()->codecForName( usedCS, ok );
00193 
00194   if ( !ok ) {
00195     //no codec available => try local8Bit and hope the best ;-)
00196     usedCS = KGlobal::locale()->encoding();
00197     codec = KGlobal::charsets()->codecForName( usedCS, ok );
00198   }
00199 
00200   if ( usedCS.contains( "8859-" ) ) { // use "B"-Encoding for non iso-8859-x charsets
00201     useQEncoding = true;
00202   }
00203 
00204   encoded8Bit = codec->fromUnicode( src );
00205 
00206   if ( allow8BitHeaders ) {
00207     return encoded8Bit;
00208   }
00209 
00210   uint encoded8BitLength = encoded8Bit.length();
00211   for ( unsigned int i=0; i<encoded8BitLength; i++ ) {
00212     if ( encoded8Bit[i] == ' ' ) { // encoding starts at word boundaries
00213       start = i + 1;
00214     }
00215 
00216     // encode escape character, for japanese encodings...
00217     if ( ( (signed char)encoded8Bit[i] < 0 ) || ( encoded8Bit[i] == '\033' ) ||
00218          ( addressHeader && ( strchr( "\"()<>@,.;:\\[]=", encoded8Bit[i] ) != 0 ) ) ) {
00219       end = start;   // non us-ascii char found, now we determine where to stop encoding
00220       nonAscii = true;
00221       break;
00222     }
00223   }
00224 
00225   if ( nonAscii ) {
00226     while ( ( end < encoded8Bit.length() ) && ( encoded8Bit[end] != ' ' ) ) {
00227       // we encode complete words
00228       end++;
00229     }
00230 
00231     for ( int x=end; x<encoded8Bit.length(); x++ ) {
00232       if ( ( (signed char)encoded8Bit[x]<0) || ( encoded8Bit[x] == '\033' ) ||
00233            ( addressHeader && ( strchr("\"()<>@,.;:\\[]=",encoded8Bit[x]) != 0 ) ) ) {
00234         end = encoded8Bit.length();     // we found another non-ascii word
00235 
00236         while ( ( end < encoded8Bit.length() ) && ( encoded8Bit[end] != ' ' ) ) {
00237           // we encode complete words
00238           end++;
00239         }
00240       }
00241     }
00242 
00243     result = encoded8Bit.left( start ) + "=?" + usedCS;
00244 
00245     if ( useQEncoding ) {
00246       result += "?Q?";
00247 
00248       char c, hexcode;// "Q"-encoding implementation described in RFC 2047
00249       for ( int i=start; i<end; i++ ) {
00250         c = encoded8Bit[i];
00251         if ( c == ' ' ) { // make the result readable with not MIME-capable readers
00252           result += '_';
00253         } else {
00254           if ( ( ( c >= 'a' ) && ( c <= 'z' ) ) || // paranoid mode, encode *all* special chars to avoid problems
00255               ( ( c >= 'A' ) && ( c <= 'Z' ) ) ||  // with "From" & "To" headers
00256               ( ( c >= '0' ) && ( c <= '9' ) ) ) {
00257             result += c;
00258           } else {
00259             result += '=';                 // "stolen" from KMail ;-)
00260             hexcode = ((c & 0xF0) >> 4) + 48;
00261             if ( hexcode >= 58 ) {
00262               hexcode += 7;
00263             }
00264             result += hexcode;
00265             hexcode = (c & 0x0F) + 48;
00266             if ( hexcode >= 58 ) {
00267               hexcode += 7;
00268             }
00269             result += hexcode;
00270           }
00271         }
00272       }
00273     } else {
00274       result += "?B?" + encoded8Bit.mid( start, end - start ).toBase64();
00275     }
00276 
00277     result +="?=";
00278     result += encoded8Bit.right( encoded8Bit.length() - end );
00279   } else {
00280     result = encoded8Bit;
00281   }
00282 
00283   return result;
00284 }
00285 
00286 QByteArray uniqueString()
00287 {
00288   static char chars[] = "0123456789abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
00289   time_t now;
00290   char p[11];
00291   int pos, ran;
00292   unsigned int timeval;
00293 
00294   p[10] = '\0';
00295   now = time( 0 );
00296   ran = 1 + (int)(1000.0*rand() / (RAND_MAX + 1.0));
00297   timeval = (now / ran) + getpid();
00298 
00299   for ( int i=0; i<10; i++ ) {
00300     pos = (int) (61.0*rand() / (RAND_MAX + 1.0));
00301     //kDebug(5320) << pos;
00302     p[i] = chars[pos];
00303   }
00304 
00305   QByteArray ret;
00306   ret.setNum( timeval );
00307   ret += '.';
00308   ret += p;
00309 
00310   return ret;
00311 }
00312 
00313 QByteArray multiPartBoundary()
00314 {
00315   return "nextPart" + uniqueString();
00316 }
00317 
00318 QByteArray unfoldHeader( const QByteArray &header )
00319 {
00320   QByteArray result;
00321   int pos = 0, foldBegin = 0, foldMid = 0, foldEnd = 0;
00322   while ( ( foldMid = header.indexOf( '\n', pos ) ) >= 0 ) {
00323     foldBegin = foldEnd = foldMid;
00324     // find the first space before the line-break
00325     while ( foldBegin > 0 ) {
00326       if ( !QChar( header[foldBegin - 1] ).isSpace() ) {
00327         break;
00328       }
00329       --foldBegin;
00330     }
00331     // find the first non-space after the line-break
00332     while ( foldEnd <= header.length() - 1 ) {
00333       if ( !QChar( header[foldEnd] ).isSpace() ) {
00334         break;
00335       }
00336       ++foldEnd;
00337     }
00338     result += header.mid( pos, foldBegin - pos );
00339     if ( foldEnd < header.length() -1 )
00340       result += ' ';
00341     pos = foldEnd;
00342   }
00343   result += header.mid( pos, header.length() - pos );
00344   return result;
00345 }
00346 
00347 int indexOfHeader( const QByteArray &src, const QByteArray &name, int &end, int &dataBegin, bool *folded )
00348 {
00349   QByteArray n = name;
00350   n.append( ':' );
00351   int begin = -1;
00352 
00353   if ( qstrnicmp( n.constData(), src.constData(), n.length() ) == 0 ) {
00354     begin = 0;
00355   } else {
00356     n.prepend('\n');
00357     const char *p = strcasestr( src.constData(), n.constData() );
00358     if ( !p ) {
00359       begin = -1;
00360     } else {
00361       begin = p - src.constData();
00362       ++begin;
00363     }
00364   }
00365 
00366   if ( begin > -1) {     //there is a header with the given name
00367     dataBegin = begin + name.length() + 1; //skip the name
00368     // skip the usual space after the colon
00369     if ( src.at( dataBegin ) == ' ' ) {
00370       ++dataBegin;
00371     }
00372     end = dataBegin;
00373     int len = src.length() - 1;
00374     if ( folded )
00375       *folded = false;
00376 
00377     if ( src.at(end) != '\n' ) {  // check if the header is not empty
00378       while ( true ) {
00379         end = src.indexOf( '\n', end + 1 );
00380         if ( end == -1 || end == len ||
00381              ( src[end+1] != ' ' && src[end+1] != '\t' ) ) {
00382           //break if we reach the end of the string, honor folded lines
00383           break;
00384         } else {
00385           if ( folded )
00386             *folded = true;
00387         }
00388       }
00389     }
00390 
00391     if ( end < 0 ) {
00392       end = len + 1; //take the rest of the string
00393     }
00394     return begin;
00395 
00396   } else {
00397     dataBegin = -1;
00398     return -1; //header not found
00399   }
00400 }
00401 
00402 QByteArray extractHeader( const QByteArray &src, const QByteArray &name )
00403 {
00404   int begin, end;
00405   bool folded;
00406   indexOfHeader( src, name, end, begin, &folded );
00407 
00408   if ( begin >= 0 ) {
00409     if ( !folded ) {
00410       return src.mid( begin, end - begin );
00411     } else {
00412       QByteArray hdrValue = src.mid( begin, end - begin );
00413       return unfoldHeader( hdrValue );
00414     }
00415   } else {
00416     return QByteArray(); //header not found
00417   }
00418 }
00419 
00420 QList<QByteArray> extractHeaders( const QByteArray &src, const QByteArray &name )
00421 {
00422   int begin, end;
00423   bool folded;
00424   QList<QByteArray> result;
00425   QByteArray copySrc( src );
00426 
00427   indexOfHeader( copySrc, name, end, begin, &folded );
00428   while ( begin >= 0 ) {
00429     if ( !folded ) {
00430       result.append( copySrc.mid( begin, end - begin ) );
00431     } else {
00432       QByteArray hdrValue = copySrc.mid( begin, end - begin );
00433       result.append( unfoldHeader( hdrValue ) );
00434     }
00435 
00436     // get the next one, a tiny bit ugly, but we don't want the previous to be found again...
00437     copySrc = copySrc.mid( end );
00438     indexOfHeader( copySrc, name, end, begin, &folded );
00439   }
00440 
00441   return result;
00442 }
00443 
00444 void removeHeader( QByteArray &header, const QByteArray &name )
00445 {
00446   int begin, end, dummy;
00447   begin = indexOfHeader( header, name, end, dummy );
00448   if ( begin >= 0 ) {
00449     header.remove( begin, end - begin + 1 );
00450   }
00451 }
00452 
00453 QByteArray CRLFtoLF( const QByteArray &s )
00454 {
00455   QByteArray ret = s;
00456   ret.replace( "\r\n", "\n" );
00457   return ret;
00458 }
00459 
00460 QByteArray LFtoCRLF( const QByteArray &s )
00461 {
00462   QByteArray ret = s;
00463   ret.replace( "\n", "\r\n" );
00464   return ret;
00465 }
00466 
00467 namespace {
00468 template < typename T > void removeQuotesGeneric( T & str )
00469 {
00470   bool inQuote = false;
00471   for ( int i = 0; i < str.length(); ++i ) {
00472     if ( str[i] == '"' ) {
00473       str.remove( i, 1 );
00474       i--;
00475       inQuote = !inQuote;
00476     } else {
00477       if ( inQuote && ( str[i] == '\\' ) ) {
00478         str.remove( i, 1 );
00479       }
00480     }
00481   }
00482 }
00483 }
00484 
00485 void removeQuots( QByteArray &str )
00486 {
00487   removeQuotesGeneric( str );
00488 }
00489 
00490 void removeQuots( QString &str )
00491 {
00492   removeQuotesGeneric( str );
00493 }
00494 
00495 void addQuotes( QByteArray &str, bool forceQuotes )
00496 {
00497   bool needsQuotes=false;
00498   for ( int i=0; i < str.length(); i++ ) {
00499     if ( strchr("()<>@,.;:[]=\\\"", str[i] ) != 0 ) {
00500       needsQuotes = true;
00501     }
00502     if ( str[i] == '\\' || str[i] == '\"' ) {
00503       str.insert( i, '\\' );
00504       i++;
00505     }
00506   }
00507 
00508   if ( needsQuotes || forceQuotes ) {
00509     str.insert( 0, '\"' );
00510     str.append( "\"" );
00511   }
00512 }
00513 
00514 } // namespace KMime

KMIME Library

Skip menu "KMIME Library"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  • kabc
  • kblog
  • kcal
  • kimap
  • kioslave
  •   imap4
  •   mbox
  • kldap
  • kmime
  • kpimidentities
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.5.6
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal