• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.9.4 API Reference
  • KDE Home
  • Contact Us
 

KMIME Library

  • kmime
kmime_util.cpp
1 /*
2  kmime_util.cpp
3 
4  KMime, the KDE Internet mail/usenet news message library.
5  Copyright (c) 2001 the KMime authors.
6  See file AUTHORS for details
7 
8  This library is free software; you can redistribute it and/or
9  modify it under the terms of the GNU Library General Public
10  License as published by the Free Software Foundation; either
11  version 2 of the License, or (at your option) any later version.
12 
13  This library is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  Library General Public License for more details.
17 
18  You should have received a copy of the GNU Library General Public License
19  along with this library; see the file COPYING.LIB. If not, write to
20  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  Boston, MA 02110-1301, USA.
22 */
23 
24 #include "kmime_util.h"
25 #include "kmime_util_p.h"
26 
27 #include "kmime_charfreq.h"
28 #include "kmime_codecs.h"
29 #include "kmime_header_parsing.h"
30 #include "kmime_message.h"
31 #include "kmime_warning.h"
32 
33 #include <config-kmime.h>
34 #include <kdefakes.h> // for strcasestr
35 #include <kglobal.h>
36 #include <klocale.h>
37 #include <kcharsets.h>
38 #include <kcodecs.h>
39 #include <kdebug.h>
40 
41 #include <QtCore/QList>
42 #include <QtCore/QString>
43 #include <QtCore/QTextCodec>
44 
45 #include <ctype.h>
46 #include <time.h>
47 #include <stdlib.h>
48 #include <unistd.h>
49 #include <boost/concept_check.hpp>
50 
51 using namespace KMime;
52 
53 namespace KMime {
54 
55 QList<QByteArray> c_harsetCache;
56 QList<QByteArray> l_anguageCache;
57 QString f_allbackCharEnc;
58 bool u_seOutlookEncoding = false;
59 
60 QByteArray cachedCharset( const QByteArray &name )
61 {
62  foreach ( const QByteArray& charset, c_harsetCache ) {
63  if ( qstricmp( name.data(), charset.data() ) == 0 ) {
64  return charset;
65  }
66  }
67 
68  c_harsetCache.append( name.toUpper() );
69  //kDebug() << "KNMimeBase::cachedCharset() number of cs" << c_harsetCache.count();
70  return c_harsetCache.last();
71 }
72 
73 QByteArray cachedLanguage( const QByteArray &name )
74 {
75  foreach ( const QByteArray& language, l_anguageCache ) {
76  if ( qstricmp( name.data(), language.data() ) == 0 ) {
77  return language;
78  }
79  }
80 
81  l_anguageCache.append( name.toUpper() );
82  //kDebug() << "KNMimeBase::cachedCharset() number of cs" << c_harsetCache.count();
83  return l_anguageCache.last();
84 }
85 
86 bool isUsAscii( const QString &s )
87 {
88  uint sLength = s.length();
89  for ( uint i=0; i<sLength; i++ ) {
90  if ( s.at( i ).toLatin1() <= 0 ) { // c==0: non-latin1, c<0: non-us-ascii
91  return false;
92  }
93  }
94  return true;
95 }
96 
97 QString nameForEncoding( Headers::contentEncoding enc )
98 {
99  switch( enc ) {
100  case Headers::CE7Bit: return QString::fromLatin1( "7bit" );
101  case Headers::CE8Bit: return QString::fromLatin1( "8bit" );
102  case Headers::CEquPr: return QString::fromLatin1( "quoted-printable" );
103  case Headers::CEbase64: return QString::fromLatin1( "base64" );
104  case Headers::CEuuenc: return QString::fromLatin1( "uuencode" );
105  case Headers::CEbinary: return QString::fromLatin1( "binary" );
106  default: return QString::fromLatin1( "unknown" );
107  }
108 }
109 
110 QList<Headers::contentEncoding> encodingsForData( const QByteArray &data )
111 {
112  QList<Headers::contentEncoding> allowed;
113  CharFreq cf( data );
114 
115  switch ( cf.type() ) {
116  case CharFreq::SevenBitText:
117  allowed << Headers::CE7Bit;
118  case CharFreq::EightBitText:
119  allowed << Headers::CE8Bit;
120  case CharFreq::SevenBitData:
121  if ( cf.printableRatio() > 5.0/6.0 ) {
122  // let n the length of data and p the number of printable chars.
123  // Then base64 \approx 4n/3; qp \approx p + 3(n-p)
124  // => qp < base64 iff p > 5n/6.
125  allowed << Headers::CEquPr;
126  allowed << Headers::CEbase64;
127  } else {
128  allowed << Headers::CEbase64;
129  allowed << Headers::CEquPr;
130  }
131  break;
132  case CharFreq::EightBitData:
133  allowed << Headers::CEbase64;
134  break;
135  case CharFreq::None:
136  default:
137  Q_ASSERT( false );
138  }
139 
140  return allowed;
141 }
142 
143 // "(),.:;<>@[\]
144 const uchar specialsMap[16] = {
145  0x00, 0x00, 0x00, 0x00, // CTLs
146  0x20, 0xCA, 0x00, 0x3A, // SPACE ... '?'
147  0x80, 0x00, 0x00, 0x1C, // '@' ... '_'
148  0x00, 0x00, 0x00, 0x00 // '`' ... DEL
149 };
150 
151 // "(),:;<>@[\]/=?
152 const uchar tSpecialsMap[16] = {
153  0x00, 0x00, 0x00, 0x00, // CTLs
154  0x20, 0xC9, 0x00, 0x3F, // SPACE ... '?'
155  0x80, 0x00, 0x00, 0x1C, // '@' ... '_'
156  0x00, 0x00, 0x00, 0x00 // '`' ... DEL
157 };
158 
159 // all except specials, CTLs, SPACE.
160 const uchar aTextMap[16] = {
161  0x00, 0x00, 0x00, 0x00,
162  0x5F, 0x35, 0xFF, 0xC5,
163  0x7F, 0xFF, 0xFF, 0xE3,
164  0xFF, 0xFF, 0xFF, 0xFE
165 };
166 
167 // all except tspecials, CTLs, SPACE.
168 const uchar tTextMap[16] = {
169  0x00, 0x00, 0x00, 0x00,
170  0x5F, 0x36, 0xFF, 0xC0,
171  0x7F, 0xFF, 0xFF, 0xE3,
172  0xFF, 0xFF, 0xFF, 0xFE
173 };
174 
175 // none except a-zA-Z0-9!*+-/
176 const uchar eTextMap[16] = {
177  0x00, 0x00, 0x00, 0x00,
178  0x40, 0x35, 0xFF, 0xC0,
179  0x7F, 0xFF, 0xFF, 0xE0,
180  0x7F, 0xFF, 0xFF, 0xE0
181 };
182 
183 void setFallbackCharEncoding(const QString& fallbackCharEnc)
184 {
185  f_allbackCharEnc = fallbackCharEnc;
186 }
187 
188 QString fallbackCharEncoding()
189 {
190  return f_allbackCharEnc;
191 }
192 
193 void setUseOutlookAttachmentEncoding( bool violateStandard )
194 {
195  u_seOutlookEncoding = violateStandard;
196 }
197 
198 bool useOutlookAttachmentEncoding()
199 {
200  return u_seOutlookEncoding;
201 }
202 
203 
204 QString decodeRFC2047String( const QByteArray &src, QByteArray &usedCS,
205  const QByteArray &defaultCS, bool forceCS )
206 {
207  QByteArray result;
208  QByteArray spaceBuffer;
209  const char *scursor = src.constData();
210  const char *send = scursor + src.length();
211  bool onlySpacesSinceLastWord = false;
212 
213  while ( scursor != send ) {
214  // space
215  if ( isspace( *scursor ) && onlySpacesSinceLastWord ) {
216  spaceBuffer += *scursor++;
217  continue;
218  }
219 
220  // possible start of an encoded word
221  if ( *scursor == '=' ) {
222  QByteArray language;
223  QString decoded;
224  ++scursor;
225  const char *start = scursor;
226  if ( HeaderParsing::parseEncodedWord( scursor, send, decoded, language, usedCS, defaultCS, forceCS ) ) {
227  result += decoded.toUtf8();
228  onlySpacesSinceLastWord = true;
229  spaceBuffer.clear();
230  } else {
231  if ( onlySpacesSinceLastWord ) {
232  result += spaceBuffer;
233  onlySpacesSinceLastWord = false;
234  }
235  result += '=';
236  scursor = start; // reset cursor after parsing failure
237  }
238  continue;
239  } else {
240  // unencoded data
241  if ( onlySpacesSinceLastWord ) {
242  result += spaceBuffer;
243  onlySpacesSinceLastWord = false;
244  }
245  result += *scursor;
246  ++scursor;
247  }
248  }
249  // If there are any chars that couldn't be decoded in UTF-8,
250  // use the fallback charset if it exists
251  const QString tryUtf8 = QString::fromUtf8( result );
252  if ( tryUtf8.contains( 0xFFFD ) && !f_allbackCharEnc.isEmpty() ) {
253  QTextCodec* codec = KGlobal::charsets()->codecForName( f_allbackCharEnc );
254  return codec->toUnicode( result );
255  } else {
256  return tryUtf8;
257  }
258 }
259 
260 QString decodeRFC2047String( const QByteArray &src )
261 {
262  QByteArray usedCS;
263  return decodeRFC2047String( src, usedCS, "utf-8", false );
264 }
265 
266 static const char *reservedCharacters = "\"()<>@,.;:\\[]=";
267 
268 QByteArray encodeRFC2047String( const QString &src, const QByteArray &charset,
269  bool addressHeader, bool allow8BitHeaders )
270 {
271  QByteArray result;
272  int start=0, end=0;
273  bool nonAscii=false, ok=true, useQEncoding=false;
274 
275  // fromLatin1() is safe here, codecForName() uses toLatin1() internally
276  const QTextCodec *codec = KGlobal::charsets()->codecForName( QString::fromLatin1( charset ), ok );
277 
278  QByteArray usedCS;
279  if ( !ok ) {
280  //no codec available => try local8Bit and hope the best ;-)
281  usedCS = KGlobal::locale()->encoding();
282  codec = KGlobal::charsets()->codecForName( QString::fromLatin1( usedCS ), ok );
283  }
284  else {
285  Q_ASSERT( codec );
286  if ( charset.isEmpty() )
287  usedCS = codec->name();
288  else
289  usedCS = charset;
290  }
291 
292  QTextCodec::ConverterState converterState( QTextCodec::IgnoreHeader );
293  QByteArray encoded8Bit = codec->fromUnicode( src.constData(), src.length(), &converterState );
294  if ( converterState.invalidChars > 0 ) {
295  usedCS = "utf-8";
296  codec = QTextCodec::codecForName( usedCS );
297  encoded8Bit = codec->fromUnicode( src );
298  }
299 
300  if ( usedCS.contains( "8859-" ) ) { // use "B"-Encoding for non iso-8859-x charsets
301  useQEncoding = true;
302  }
303 
304  if ( allow8BitHeaders ) {
305  return encoded8Bit;
306  }
307 
308  uint encoded8BitLength = encoded8Bit.length();
309  for ( unsigned int i=0; i<encoded8BitLength; i++ ) {
310  if ( encoded8Bit[i] == ' ' ) { // encoding starts at word boundaries
311  start = i + 1;
312  }
313 
314  // encode escape character, for japanese encodings...
315  if ( ( (signed char)encoded8Bit[i] < 0 ) || ( encoded8Bit[i] == '\033' ) ||
316  ( addressHeader && ( strchr( "\"()<>@,.;:\\[]=", encoded8Bit[i] ) != 0 ) ) ) {
317  end = start; // non us-ascii char found, now we determine where to stop encoding
318  nonAscii = true;
319  break;
320  }
321  }
322 
323  if ( nonAscii ) {
324  while ( ( end < encoded8Bit.length() ) && ( encoded8Bit[end] != ' ' ) ) {
325  // we encode complete words
326  end++;
327  }
328 
329  for ( int x=end; x<encoded8Bit.length(); x++ ) {
330  if ( ( (signed char)encoded8Bit[x]<0) || ( encoded8Bit[x] == '\033' ) ||
331  ( addressHeader && ( strchr(reservedCharacters, encoded8Bit[x]) != 0 ) ) ) {
332  end = x; // we found another non-ascii word
333 
334  while ( ( end < encoded8Bit.length() ) && ( encoded8Bit[end] != ' ' ) ) {
335  // we encode complete words
336  end++;
337  }
338  }
339  }
340 
341  result = encoded8Bit.left( start ) + "=?" + usedCS;
342 
343  if ( useQEncoding ) {
344  result += "?Q?";
345 
346  char c, hexcode;// "Q"-encoding implementation described in RFC 2047
347  for ( int i=start; i<end; i++ ) {
348  c = encoded8Bit[i];
349  if ( c == ' ' ) { // make the result readable with not MIME-capable readers
350  result += '_';
351  } else {
352  if ( ( ( c >= 'a' ) && ( c <= 'z' ) ) || // paranoid mode, encode *all* special chars to avoid problems
353  ( ( c >= 'A' ) && ( c <= 'Z' ) ) || // with "From" & "To" headers
354  ( ( c >= '0' ) && ( c <= '9' ) ) ) {
355  result += c;
356  } else {
357  result += '='; // "stolen" from KMail ;-)
358  hexcode = ((c & 0xF0) >> 4) + 48;
359  if ( hexcode >= 58 ) {
360  hexcode += 7;
361  }
362  result += hexcode;
363  hexcode = (c & 0x0F) + 48;
364  if ( hexcode >= 58 ) {
365  hexcode += 7;
366  }
367  result += hexcode;
368  }
369  }
370  }
371  } else {
372  result += "?B?" + encoded8Bit.mid( start, end - start ).toBase64();
373  }
374 
375  result +="?=";
376  result += encoded8Bit.right( encoded8Bit.length() - end );
377  } else {
378  result = encoded8Bit;
379  }
380 
381  return result;
382 }
383 
384 QByteArray encodeRFC2047Sentence(const QString& src, const QByteArray& charset )
385 {
386  QByteArray result;
387  QList<QChar> splitChars;
388  splitChars << QLatin1Char(',') << QLatin1Char('\"') << QLatin1Char(';') << QLatin1Char('\\');
389  const QChar *ch = src.constData();
390  const int length = src.length();
391  int pos = 0;
392  int wordStart = 0;
393 
394  //qDebug() << "Input:" << src;
395  // Loop over all characters of the string.
396  // When encountering a split character, RFC-2047-encode the word before it, and add it to the result.
397  while (pos < length) {
398  //qDebug() << "Pos:" << pos << "Result:" << result << "Char:" << ch->toAscii();
399  const bool isAscii = ch->unicode() < 127;
400  const bool isReserved = (strchr( reservedCharacters, ch->toAscii() ) != 0);
401  if ( isAscii && isReserved ) {
402  const int wordSize = pos - wordStart;
403  if (wordSize > 0) {
404  const QString word = src.mid( wordStart, wordSize );
405  result += encodeRFC2047String( word, charset );
406  }
407 
408  result += ch->toAscii();
409  wordStart = pos + 1;
410  }
411  ch++;
412  pos++;
413  }
414 
415  // Encode the last word
416  const int wordSize = pos - wordStart;
417  if (wordSize > 0) {
418  const QString word = src.mid( wordStart, pos - wordStart );
419  result += encodeRFC2047String( word, charset );
420  }
421 
422  return result;
423 }
424 
425 
426 
427 //-----------------------------------------------------------------------------
428 QByteArray encodeRFC2231String( const QString& str, const QByteArray& charset )
429 {
430  if ( str.isEmpty() )
431  return QByteArray();
432 
433 
434  const QTextCodec *codec = KGlobal::charsets()->codecForName( QString::fromLatin1( charset ) );
435  QByteArray latin;
436  if ( charset == "us-ascii" )
437  latin = str.toAscii();
438  else if ( codec )
439  latin = codec->fromUnicode( str );
440  else
441  latin = str.toLocal8Bit();
442 
443  char *l;
444  for ( l = latin.data(); *l; ++l ) {
445  if ( ( ( *l & 0xE0 ) == 0 ) || ( *l & 0x80 ) )
446  // *l is control character or 8-bit char
447  break;
448  }
449  if ( !*l )
450  return latin;
451 
452  QByteArray result = charset + "''";
453  for ( l = latin.data(); *l; ++l ) {
454  bool needsQuoting = ( *l & 0x80 ) || ( *l == '%' );
455  if( !needsQuoting ) {
456  const QByteArray especials = "()<>@,;:\"/[]?.= \033";
457  int len = especials.length();
458  for ( int i = 0; i < len; i++ )
459  if ( *l == especials[i] ) {
460  needsQuoting = true;
461  break;
462  }
463  }
464  if ( needsQuoting ) {
465  result += '%';
466  unsigned char hexcode;
467  hexcode = ( ( *l & 0xF0 ) >> 4 ) + 48;
468  if ( hexcode >= 58 )
469  hexcode += 7;
470  result += hexcode;
471  hexcode = ( *l & 0x0F ) + 48;
472  if ( hexcode >= 58 )
473  hexcode += 7;
474  result += hexcode;
475  } else {
476  result += *l;
477  }
478  }
479  return result;
480 }
481 
482 
483 //-----------------------------------------------------------------------------
484 QString decodeRFC2231String( const QByteArray &str, QByteArray &usedCS, const QByteArray &defaultCS,
485  bool forceCS )
486 {
487  int p = str.indexOf('\'');
488  if (p < 0) return KGlobal::charsets()->codecForName( QString::fromLatin1( defaultCS ))->toUnicode( str );
489 
490 
491  QByteArray charset = str.left(p);
492 
493  QByteArray st = str.mid( str.lastIndexOf('\'') + 1 );
494 
495  char ch, ch2;
496  p = 0;
497  while (p < (int)st.length())
498  {
499  if (st.at(p) == 37)
500  {
501  // Only try to decode the percent-encoded character if the percent sign
502  // is really followed by two other characters, see testcase at bug 163024
503  if ( p + 2 < st.length() ) {
504  ch = st.at(p+1) - 48;
505  if (ch > 16)
506  ch -= 7;
507  ch2 = st.at(p+2) - 48;
508  if (ch2 > 16)
509  ch2 -= 7;
510  st[p] = ch * 16 + ch2;
511  st.remove( p+1, 2 );
512  }
513  }
514  p++;
515  }
516  kDebug() << "Got pre-decoded:" << st;
517  QString result;
518  const QTextCodec * charsetcodec = KGlobal::charsets()->codecForName( QString::fromLatin1( charset ) );
519  if ( !charsetcodec || forceCS )
520  charsetcodec = KGlobal::charsets()->codecForName( QString::fromLatin1( defaultCS ) );
521 
522  usedCS = charsetcodec->name();
523  return charsetcodec->toUnicode( st );
524 }
525 
526 QString decodeRFC2231String( const QByteArray &src )
527 {
528  QByteArray usedCS;
529  return decodeRFC2231String( src, usedCS, "utf-8", false );
530 }
531 
532 QByteArray uniqueString()
533 {
534  static char chars[] = "0123456789abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
535  time_t now;
536  char p[11];
537  int pos, ran;
538  unsigned int timeval;
539 
540  p[10] = '\0';
541  now = time( 0 );
542  ran = 1 + (int)(1000.0*rand() / (RAND_MAX + 1.0));
543  timeval = (now / ran) + getpid();
544 
545  for ( int i=0; i<10; i++ ) {
546  pos = (int) (61.0*rand() / (RAND_MAX + 1.0));
547  //kDebug() << pos;
548  p[i] = chars[pos];
549  }
550 
551  QByteArray ret;
552  ret.setNum( timeval );
553  ret += '.';
554  ret += p;
555 
556  return ret;
557 }
558 
559 QByteArray multiPartBoundary()
560 {
561  return "nextPart" + uniqueString();
562 }
563 
564 QByteArray unfoldHeader( const QByteArray &header )
565 {
566  QByteArray result;
567  if ( header.isEmpty() ) {
568  return result;
569  }
570 
571  int pos = 0, foldBegin = 0, foldMid = 0, foldEnd = 0;
572  while ( ( foldMid = header.indexOf( '\n', pos ) ) >= 0 ) {
573  foldBegin = foldEnd = foldMid;
574  // find the first space before the line-break
575  while ( foldBegin > 0 ) {
576  if ( !QChar::fromLatin1( header[foldBegin - 1] ).isSpace() ) {
577  break;
578  }
579  --foldBegin;
580  }
581  // find the first non-space after the line-break
582  while ( foldEnd <= header.length() - 1 ) {
583  if ( QChar::fromLatin1( header[foldEnd] ).isSpace() ) {
584  ++foldEnd;
585  }
586  else if ( foldEnd > 0 && header[foldEnd - 1] == '\n' &&
587  header[foldEnd] == '=' && foldEnd + 2 < header.length() &&
588  ( ( header[foldEnd + 1] == '0' &&
589  header[foldEnd + 2] == '9' ) ||
590  ( header[foldEnd + 1] == '2' &&
591  header[foldEnd + 2] == '0' ) ) ) {
592  // bug #86302: malformed header continuation starting with =09/=20
593  foldEnd += 3;
594  }
595  else {
596  break;
597  }
598  }
599 
600  result += header.mid( pos, foldBegin - pos );
601  if ( foldEnd < header.length() -1 )
602  result += ' ';
603  pos = foldEnd;
604  }
605  const int len = header.length();
606  if ( len > pos ) {
607  result += header.mid( pos, len - pos );
608  }
609  return result;
610 }
611 
612 int findHeaderLineEnd( const QByteArray &src, int &dataBegin, bool *folded )
613 {
614  int end = dataBegin;
615  int len = src.length() - 1;
616 
617  if ( folded )
618  *folded = false;
619 
620  if ( dataBegin < 0 ) {
621  // Not found
622  return -1;
623  }
624 
625  if ( dataBegin > len ) {
626  // No data available
627  return len + 1;
628  }
629 
630  // If the first line contains nothing, but the next line starts with a space
631  // or a tab, that means a stupid mail client has made the first header field line
632  // entirely empty, and has folded the rest to the next line(s).
633  if ( src.at(end) == '\n' && end + 1 < len &&
634  ( src[end+1] == ' ' || src[end+1] == '\t' ) ) {
635 
636  // Skip \n and first whitespace
637  dataBegin += 2;
638  end += 2;
639  }
640 
641  if ( src.at(end) != '\n' ) { // check if the header is not empty
642  while ( true ) {
643  end = src.indexOf( '\n', end + 1 );
644  if ( end == -1 || end == len ) {
645  // end of string
646  break;
647  }
648  else if ( src[end+1] == ' ' || src[end+1] == '\t' ||
649  ( src[end+1] == '=' && end+3 <= len &&
650  ( ( src[end+2] == '0' && src[end+3] == '9' ) ||
651  ( src[end+2] == '2' && src[end+3] == '0' ) ) ) ) {
652  // next line is header continuation or starts with =09/=20 (bug #86302)
653  if ( folded )
654  *folded = true;
655  } else {
656  // end of header (no header continuation)
657  break;
658  }
659  }
660  }
661 
662  if ( end < 0 ) {
663  end = len + 1; //take the rest of the string
664  }
665  return end;
666 }
667 
668 int indexOfHeader( const QByteArray &src, const QByteArray &name, int &end, int &dataBegin, bool *folded )
669 {
670  QByteArray n = name;
671  n.append( ':' );
672  int begin = -1;
673 
674  if ( qstrnicmp( n.constData(), src.constData(), n.length() ) == 0 ) {
675  begin = 0;
676  } else {
677  n.prepend('\n');
678  const char *p = strcasestr( src.constData(), n.constData() );
679  if ( !p ) {
680  begin = -1;
681  } else {
682  begin = p - src.constData();
683  ++begin;
684  }
685  }
686 
687  if ( begin > -1) { //there is a header with the given name
688  dataBegin = begin + name.length() + 1; //skip the name
689  // skip the usual space after the colon
690  if ( src.at( dataBegin ) == ' ' ) {
691  ++dataBegin;
692  }
693  end = findHeaderLineEnd( src, dataBegin, folded );
694  return begin;
695 
696  } else {
697  end = -1;
698  dataBegin = -1;
699  return -1; //header not found
700  }
701 }
702 
703 QByteArray extractHeader( const QByteArray &src, const QByteArray &name )
704 {
705  int begin, end;
706  bool folded;
707  QByteArray result;
708 
709  if ( src.isEmpty() || indexOfHeader( src, name, end, begin, &folded ) < 0 ) {
710  return result;
711  }
712 
713  if ( begin >= 0 ) {
714  if ( !folded ) {
715  result = src.mid( begin, end - begin );
716  } else {
717  if ( end > begin ) {
718  QByteArray hdrValue = src.mid( begin, end - begin );
719  result = unfoldHeader( hdrValue );
720  }
721  }
722  }
723  return result;
724 }
725 
726 QList<QByteArray> extractHeaders( const QByteArray &src, const QByteArray &name )
727 {
728  int begin, end;
729  bool folded;
730  QList<QByteArray> result;
731  QByteArray copySrc( src );
732 
733  if ( indexOfHeader( copySrc, name, end, begin, &folded ) < 0 ) {
734  return result;
735  }
736 
737  while ( begin >= 0 ) {
738  if ( !folded ) {
739  result.append( copySrc.mid( begin, end - begin ) );
740  } else {
741  QByteArray hdrValue = copySrc.mid( begin, end - begin );
742  result.append( unfoldHeader( hdrValue ) );
743  }
744 
745  // get the next one, a tiny bit ugly, but we don't want the previous to be found again...
746  copySrc = copySrc.mid( end );
747  if ( indexOfHeader( copySrc, name, end, begin, &folded ) < 0 ) {
748  break;
749  }
750  }
751 
752  return result;
753 }
754 
755 void removeHeader( QByteArray &header, const QByteArray &name )
756 {
757  int begin, end, dummy;
758  begin = indexOfHeader( header, name, end, dummy );
759  if ( begin >= 0 ) {
760  header.remove( begin, end - begin + 1 );
761  }
762 }
763 
764 QByteArray CRLFtoLF( const QByteArray &s )
765 {
766  QByteArray ret = s;
767  ret.replace( "\r\n", "\n" );
768  return ret;
769 }
770 
771 QByteArray CRLFtoLF( const char *s )
772 {
773  QByteArray ret = s;
774  return CRLFtoLF( ret );
775 }
776 
777 QByteArray LFtoCRLF( const QByteArray &s )
778 {
779  QByteArray ret = s;
780  ret.replace( '\n', "\r\n" );
781  return ret;
782 }
783 
784 QByteArray LFtoCRLF( const char *s )
785 {
786  QByteArray ret = s;
787  return LFtoCRLF( ret );
788 }
789 
790 namespace {
791 template < typename StringType, typename CharType > void removeQuotesGeneric( StringType & str )
792 {
793  bool inQuote = false;
794  for ( int i = 0; i < str.length(); ++i ) {
795  if ( str[i] == CharType( '"' ) ) {
796  str.remove( i, 1 );
797  i--;
798  inQuote = !inQuote;
799  } else {
800  if ( inQuote && ( str[i] == CharType( '\\' ) ) ) {
801  str.remove( i, 1 );
802  }
803  }
804  }
805 }
806 }
807 
808 void removeQuots( QByteArray &str )
809 {
810  removeQuotesGeneric<QByteArray,char>( str );
811 }
812 
813 void removeQuots( QString &str )
814 {
815  removeQuotesGeneric<QString,QLatin1Char>( str );
816 }
817 
818 template<class StringType,class CharType,class CharConverterType,class StringConverterType,class ToString>
819 void addQuotes_impl( StringType &str, bool forceQuotes )
820 {
821  bool needsQuotes=false;
822  for ( int i=0; i < str.length(); i++ ) {
823  const CharType cur = str.at( i );
824  if ( QString( ToString( str ) ).contains( QRegExp( QLatin1String( "\"|\\\\|=|\\]|\\[|:|;|,|\\.|,|@|<|>|\\)|\\(" ) ) ) ) {
825  needsQuotes = true;
826  }
827  if ( cur == CharConverterType( '\\' ) || cur == CharConverterType( '\"' ) ) {
828  str.insert( i, CharConverterType( '\\' ) );
829  i++;
830  }
831  }
832 
833  if ( needsQuotes || forceQuotes ) {
834  str.insert( 0, CharConverterType( '\"' ) );
835  str.append( StringConverterType( "\"" ) );
836  }
837 }
838 
839 void addQuotes( QByteArray &str, bool forceQuotes )
840 {
841  addQuotes_impl<QByteArray,char,char,char*,QLatin1String>( str, forceQuotes );
842 }
843 
844 void addQuotes( QString &str, bool forceQuotes )
845 {
846  addQuotes_impl<QString,QChar,QLatin1Char,QLatin1String,QString>( str, forceQuotes );
847 }
848 
849 KMIME_EXPORT QString balanceBidiState( const QString &input )
850 {
851  const int LRO = 0x202D;
852  const int RLO = 0x202E;
853  const int LRE = 0x202A;
854  const int RLE = 0x202B;
855  const int PDF = 0x202C;
856 
857  QString result = input;
858 
859  int openDirChangers = 0;
860  int numPDFsRemoved = 0;
861  for ( int i = 0; i < input.length(); i++ ) {
862  const ushort &code = input.at( i ).unicode();
863  if ( code == LRO || code == RLO || code == LRE || code == RLE ) {
864  openDirChangers++;
865  }
866  else if ( code == PDF ) {
867  if ( openDirChangers > 0 ) {
868  openDirChangers--;
869  }
870  else {
871  // One PDF too much, remove it
872  kWarning() << "Possible Unicode spoofing (unexpected PDF) detected in" << input;
873  result.remove( i - numPDFsRemoved, 1 );
874  numPDFsRemoved++;
875  }
876  }
877  }
878 
879  if ( openDirChangers > 0 ) {
880  kWarning() << "Possible Unicode spoofing detected in" << input;
881 
882  // At PDF chars to the end until the correct state is restored.
883  // As a special exception, when encountering quoted strings, place the PDF before
884  // the last quote.
885  for ( int i = openDirChangers; i > 0; i-- ) {
886  if ( result.endsWith( QLatin1Char( '"' ) ) )
887  result.insert( result.length() - 1, QChar( PDF ) );
888  else
889  result += QChar( PDF );
890  }
891  }
892 
893  return result;
894 }
895 
896 QString removeBidiControlChars( const QString &input )
897 {
898  const int LRO = 0x202D;
899  const int RLO = 0x202E;
900  const int LRE = 0x202A;
901  const int RLE = 0x202B;
902  QString result = input;
903  result.remove( LRO );
904  result.remove( RLO );
905  result.remove( LRE );
906  result.remove( RLE );
907  return result;
908 }
909 
910 static bool isCryptoPart( Content* content )
911 {
912  if( !content->contentType( false ) )
913  return false;
914 
915  if( content->contentType()->subType().toLower() == "octet-stream" &&
916  !content->contentDisposition( false ) )
917  return false;
918 
919  const Headers::ContentType *contentType = content->contentType();
920  const QByteArray lowerSubType = contentType->subType().toLower();
921  return ( contentType->mediaType().toLower() == "application" &&
922  ( lowerSubType == "pgp-encrypted" ||
923  lowerSubType == "pgp-signature" ||
924  lowerSubType == "pkcs7-mime" ||
925  lowerSubType == "pkcs7-signature" ||
926  lowerSubType == "x-pkcs7-signature" ||
927  ( lowerSubType == "octet-stream" &&
928  content->contentDisposition()->filename().toLower() == QLatin1String( "msg.asc" ) ) ) );
929 }
930 
931 bool hasAttachment( Content* content )
932 {
933  if( !content )
934  return false;
935 
936  bool emptyFilename = true;
937  if( content->contentDisposition( false ) && !content->contentDisposition()->filename().isEmpty() )
938  emptyFilename = false;
939 
940  if( emptyFilename && content->contentType( false ) && !content->contentType()->name().isEmpty() )
941  emptyFilename = false;
942 
943  // ignore crypto parts
944  if( !emptyFilename && !isCryptoPart( content ) )
945  return true;
946 
947  // Ok, content itself is not an attachment. now we deal with multiparts
948  if( content->contentType()->isMultipart() ) {
949  Q_FOREACH( Content* child, content->contents() ) {
950  if( hasAttachment( child ) )
951  return true;
952  }
953  }
954 
955  return false;
956 }
957 
958 bool isSigned( Message *message )
959 {
960  if ( !message )
961  return false;
962 
963  const KMime::Headers::ContentType* const contentType = message->contentType();
964  if ( contentType->isSubtype( "signed" ) ||
965  contentType->isSubtype( "pgp-signature" ) ||
966  contentType->isSubtype( "pkcs7-signature" ) ||
967  contentType->isSubtype( "x-pkcs7-signature" ) ||
968  message->mainBodyPart( "multipart/signed" ) ||
969  message->mainBodyPart( "application/pgp-signature" ) ||
970  message->mainBodyPart( "application/pkcs7-signature" ) ||
971  message->mainBodyPart( "application/x-pkcs7-signature" ) ) {
972  return true;
973  }
974 
975  return false;
976 }
977 
978 bool isEncrypted( Message *message )
979 {
980  if ( !message )
981  return false;
982 
983  const KMime::Headers::ContentType* const contentType = message->contentType();
984  if ( contentType->isSubtype( "encrypted" ) ||
985  contentType->isSubtype( "pgp-encrypted" ) ||
986  contentType->isSubtype( "pkcs7-mime" ) ||
987  message->mainBodyPart( "multipart/encrypted" ) ||
988  message->mainBodyPart( "application/pgp-encrypted" ) ||
989  message->mainBodyPart( "application/pkcs7-mime" ) ) {
990  return true;
991  }
992 
993  return false;
994 }
995 
996 bool isInvitation( Content *content )
997 {
998  if ( !content )
999  return false;
1000 
1001  const KMime::Headers::ContentType* const contentType = content->contentType( false );
1002 
1003  if ( contentType && contentType->isMediatype( "text" ) && contentType->isSubtype( "calendar" ) )
1004  return true;
1005 
1006  return false;
1007 }
1008 
1009 } // namespace KMime
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Tue Dec 4 2012 14:35:18 by doxygen 1.8.1.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KMIME Library

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

kdepimlibs-4.9.4 API Reference

Skip menu "kdepimlibs-4.9.4 API Reference"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal