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

KMIME Library

  • sources
  • kde-4.14
  • kdepimlibs
  • 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 <klocalizedstring.h>
38 #include <kcharsets.h>
39 #include <kcodecs.h>
40 #include <kdebug.h>
41 
42 #include <QtCore/QList>
43 #include <QtCore/QString>
44 #include <QtCore/QTextCodec>
45 
46 #include <ctype.h>
47 #include <time.h>
48 #include <stdlib.h>
49 #include <unistd.h>
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  result.reserve(64);
209  QByteArray spaceBuffer;
210  spaceBuffer.reserve(64);
211  const char *scursor = src.constData();
212  const char *send = scursor + src.length();
213  bool onlySpacesSinceLastWord = false;
214 
215  while ( scursor != send ) {
216  // space
217  if ( isspace( *scursor ) && onlySpacesSinceLastWord ) {
218  spaceBuffer += *scursor++;
219  continue;
220  }
221 
222  // possible start of an encoded word
223  if ( *scursor == '=' ) {
224  QByteArray language;
225  QString decoded;
226  ++scursor;
227  const char *start = scursor;
228  if ( HeaderParsing::parseEncodedWord( scursor, send, decoded, language, usedCS, defaultCS, forceCS ) ) {
229  result += decoded.toUtf8();
230  onlySpacesSinceLastWord = true;
231  spaceBuffer.clear();
232  } else {
233  if ( onlySpacesSinceLastWord ) {
234  result += spaceBuffer;
235  onlySpacesSinceLastWord = false;
236  }
237  result += '=';
238  scursor = start; // reset cursor after parsing failure
239  }
240  continue;
241  } else {
242  // unencoded data
243  if ( onlySpacesSinceLastWord ) {
244  result += spaceBuffer;
245  onlySpacesSinceLastWord = false;
246  }
247  result += *scursor;
248  ++scursor;
249  }
250  }
251  // If there are any chars that couldn't be decoded in UTF-8,
252  // use the fallback charset if it exists
253  const QString tryUtf8 = QString::fromUtf8( result );
254  if ( tryUtf8.contains( 0xFFFD ) && !f_allbackCharEnc.isEmpty() ) {
255  QTextCodec* codec = KGlobal::charsets()->codecForName( f_allbackCharEnc );
256  return codec->toUnicode( result );
257  } else {
258  return tryUtf8;
259  }
260 }
261 
262 QString decodeRFC2047String( const QByteArray &src )
263 {
264  QByteArray usedCS;
265  return decodeRFC2047String( src, usedCS, "utf-8", false );
266 }
267 
268 static const char *reservedCharacters = "\"()<>@,.;:\\[]=";
269 
270 QByteArray encodeRFC2047String( const QString &src, const QByteArray &charset,
271  bool addressHeader, bool allow8BitHeaders )
272 {
273  QByteArray result;
274  int start=0, end=0;
275  bool nonAscii=false, ok=true, useQEncoding=false;
276 
277  // fromLatin1() is safe here, codecForName() uses toLatin1() internally
278  const QTextCodec *codec = KGlobal::charsets()->codecForName( QString::fromLatin1( charset ), ok );
279 
280  QByteArray usedCS;
281  if ( !ok ) {
282  //no codec available => try local8Bit and hope the best ;-)
283  usedCS = KGlobal::locale()->encoding();
284  codec = KGlobal::charsets()->codecForName( QString::fromLatin1( usedCS ), ok );
285  } else {
286  Q_ASSERT( codec );
287  if ( charset.isEmpty() ) {
288  usedCS = codec->name();
289  } else {
290  usedCS = charset;
291  }
292  }
293 
294  QTextCodec::ConverterState converterState( QTextCodec::IgnoreHeader );
295  QByteArray encoded8Bit = codec->fromUnicode( src.constData(), src.length(), &converterState );
296  if ( converterState.invalidChars > 0 ) {
297  usedCS = "utf-8";
298  codec = QTextCodec::codecForName( usedCS );
299  encoded8Bit = codec->fromUnicode( src );
300  }
301 
302  if ( usedCS.contains( "8859-" ) ) { // use "B"-Encoding for non iso-8859-x charsets
303  useQEncoding = true;
304  }
305 
306  if ( allow8BitHeaders ) {
307  return encoded8Bit;
308  }
309 
310  uint encoded8BitLength = encoded8Bit.length();
311  for ( unsigned int i=0; i<encoded8BitLength; i++ ) {
312  if ( encoded8Bit[i] == ' ' ) { // encoding starts at word boundaries
313  start = i + 1;
314  }
315 
316  // encode escape character, for japanese encodings...
317  if ( ( (signed char)encoded8Bit[i] < 0 ) || ( encoded8Bit[i] == '\033' ) ||
318  ( addressHeader && ( strchr( "\"()<>@,.;:\\[]=", encoded8Bit[i] ) != 0 ) ) ) {
319  end = start; // non us-ascii char found, now we determine where to stop encoding
320  nonAscii = true;
321  break;
322  }
323  }
324 
325  if ( nonAscii ) {
326  while ( ( end < encoded8Bit.length() ) && ( encoded8Bit[end] != ' ' ) ) {
327  // we encode complete words
328  end++;
329  }
330 
331  for ( int x=end; x<encoded8Bit.length(); x++ ) {
332  if ( ( (signed char)encoded8Bit[x] < 0 ) || ( encoded8Bit[x] == '\033' ) ||
333  ( addressHeader && ( strchr( reservedCharacters, encoded8Bit[x] ) != 0 ) ) ) {
334  end = x; // we found another non-ascii word
335 
336  while ( ( end < encoded8Bit.length() ) && ( encoded8Bit[end] != ' ' ) ) {
337  // we encode complete words
338  end++;
339  }
340  }
341  }
342 
343  result = encoded8Bit.left( start ) + "=?" + usedCS;
344 
345  if ( useQEncoding ) {
346  result += "?Q?";
347 
348  char c, hexcode;// "Q"-encoding implementation described in RFC 2047
349  for ( int i=start; i<end; i++ ) {
350  c = encoded8Bit[i];
351  if ( c == ' ' ) { // make the result readable with not MIME-capable readers
352  result += '_';
353  } else {
354  if ( ( ( c >= 'a' ) && ( c <= 'z' ) ) || // paranoid mode, encode *all* special chars to avoid problems
355  ( ( c >= 'A' ) && ( c <= 'Z' ) ) || // with "From" & "To" headers
356  ( ( c >= '0' ) && ( c <= '9' ) ) ) {
357  result += c;
358  } else {
359  result += '='; // "stolen" from KMail ;-)
360  hexcode = ( ( c & 0xF0 ) >> 4 ) + 48;
361  if ( hexcode >= 58 ) {
362  hexcode += 7;
363  }
364  result += hexcode;
365  hexcode = ( c & 0x0F ) + 48;
366  if ( hexcode >= 58 ) {
367  hexcode += 7;
368  }
369  result += hexcode;
370  }
371  }
372  }
373  } else {
374  result += "?B?" + encoded8Bit.mid( start, end - start ).toBase64();
375  }
376 
377  result +="?=";
378  result += encoded8Bit.right( encoded8Bit.length() - end );
379  } else {
380  result = encoded8Bit;
381  }
382 
383  return result;
384 }
385 
386 QByteArray encodeRFC2047Sentence(const QString& src, const QByteArray& charset )
387 {
388  QByteArray result;
389  QList<QChar> splitChars;
390  splitChars << QLatin1Char( ',' ) << QLatin1Char( '\"' ) << QLatin1Char( ';' ) << QLatin1Char( '\\' );
391  const QChar *ch = src.constData();
392  const int length = src.length();
393  int pos = 0;
394  int wordStart = 0;
395 
396  //qDebug() << "Input:" << src;
397  // Loop over all characters of the string.
398  // When encountering a split character, RFC-2047-encode the word before it, and add it to the result.
399  while ( pos < length ) {
400  //qDebug() << "Pos:" << pos << "Result:" << result << "Char:" << ch->toLatin1();
401  const bool isAscii = ch->unicode() < 127;
402  const bool isReserved = ( strchr( reservedCharacters, ch->toLatin1() ) != 0 );
403  if ( isAscii && isReserved ) {
404  const int wordSize = pos - wordStart;
405  if ( wordSize > 0 ) {
406  const QString word = src.mid( wordStart, wordSize );
407  result += encodeRFC2047String( word, charset );
408  }
409 
410  result += ch->toLatin1();
411  wordStart = pos + 1;
412  }
413  ch++;
414  pos++;
415  }
416 
417  // Encode the last word
418  const int wordSize = pos - wordStart;
419  if ( wordSize > 0 ) {
420  const QString word = src.mid( wordStart, pos - wordStart );
421  result += encodeRFC2047String( word, charset );
422  }
423 
424  return result;
425 }
426 
427 
428 
429 //-----------------------------------------------------------------------------
430 QByteArray encodeRFC2231String( const QString& str, const QByteArray& charset )
431 {
432  if ( str.isEmpty() ) {
433  return QByteArray();
434  }
435 
436  const QTextCodec *codec = KGlobal::charsets()->codecForName( QString::fromLatin1( charset ) );
437  QByteArray latin;
438  if ( charset == "us-ascii" ) {
439  latin = str.toLatin1();
440  } else if ( codec ) {
441  latin = codec->fromUnicode( str );
442  } else {
443  latin = str.toLocal8Bit();
444  }
445 
446  char *l;
447  for ( l = latin.data(); *l; ++l ) {
448  if ( ( ( *l & 0xE0 ) == 0 ) || ( *l & 0x80 ) ) {
449  // *l is control character or 8-bit char
450  break;
451  }
452  }
453  if ( !*l ) {
454  return latin;
455  }
456 
457  QByteArray result = charset + "''";
458  for ( l = latin.data(); *l; ++l ) {
459  bool needsQuoting = ( *l & 0x80 ) || ( *l == '%' );
460  if ( !needsQuoting ) {
461  const QByteArray especials = "()<>@,;:\"/[]?.= \033";
462  int len = especials.length();
463  for ( int i = 0; i < len; i++ ) {
464  if ( *l == especials[i] ) {
465  needsQuoting = true;
466  break;
467  }
468  }
469  }
470  if ( needsQuoting ) {
471  result += '%';
472  unsigned char hexcode;
473  hexcode = ( ( *l & 0xF0 ) >> 4 ) + 48;
474  if ( hexcode >= 58 ) {
475  hexcode += 7;
476  }
477  result += hexcode;
478  hexcode = ( *l & 0x0F ) + 48;
479  if ( hexcode >= 58 ) {
480  hexcode += 7;
481  }
482  result += hexcode;
483  } else {
484  result += *l;
485  }
486  }
487  return result;
488 }
489 
490 
491 //-----------------------------------------------------------------------------
492 QString decodeRFC2231String( const QByteArray &str, QByteArray &usedCS, const QByteArray &defaultCS,
493  bool forceCS )
494 {
495  int p = str.indexOf( '\'' );
496  if ( p < 0 ) {
497  return KGlobal::charsets()->codecForName( QString::fromLatin1( defaultCS ) )->toUnicode( str );
498  }
499 
500 
501  QByteArray charset = str.left( p );
502 
503  QByteArray st = str.mid( str.lastIndexOf( '\'' ) + 1 );
504 
505  char ch, ch2;
506  p = 0;
507  while ( p < (int)st.length() ) {
508  if ( st.at( p ) == 37 ) {
509  // Only try to decode the percent-encoded character if the percent sign
510  // is really followed by two other characters, see testcase at bug 163024
511  if ( p + 2 < st.length() ) {
512  ch = st.at( p + 1 ) - 48;
513  if ( ch > 16 ) {
514  ch -= 7;
515  }
516  ch2 = st.at( p + 2 ) - 48;
517  if ( ch2 > 16 ) {
518  ch2 -= 7;
519  }
520  st[p] = ch * 16 + ch2;
521  st.remove( p + 1, 2 );
522  }
523  }
524  p++;
525  }
526  kDebug() << "Got pre-decoded:" << st;
527  QString result;
528  const QTextCodec * charsetcodec = KGlobal::charsets()->codecForName( QString::fromLatin1( charset ) );
529  if ( !charsetcodec || forceCS ) {
530  charsetcodec = KGlobal::charsets()->codecForName( QString::fromLatin1( defaultCS ) );
531  }
532 
533  usedCS = charsetcodec->name();
534  return charsetcodec->toUnicode( st );
535 }
536 
537 QString decodeRFC2231String( const QByteArray &src )
538 {
539  QByteArray usedCS;
540  return decodeRFC2231String( src, usedCS, "utf-8", false );
541 }
542 
543 QByteArray uniqueString()
544 {
545  static char chars[] = "0123456789abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
546  time_t now;
547  char p[11];
548  int pos, ran;
549  unsigned int timeval;
550 
551  p[10] = '\0';
552  now = time( 0 );
553  ran = 1 + (int)( 1000.0 * rand() / ( RAND_MAX + 1.0 ) );
554  timeval = ( now / ran ) + getpid();
555 
556  for ( int i = 0; i < 10; i++ ) {
557  pos = (int) ( 61.0 * rand() / ( RAND_MAX + 1.0 ) );
558  //kDebug() << pos;
559  p[i] = chars[pos];
560  }
561 
562  QByteArray ret;
563  ret.setNum( timeval );
564  ret += '.';
565  ret += p;
566 
567  return ret;
568 }
569 
570 QByteArray multiPartBoundary()
571 {
572  return "nextPart" + uniqueString();
573 }
574 
575 QByteArray unfoldHeader( const QByteArray &header )
576 {
577  QByteArray result;
578  if ( header.isEmpty() ) {
579  return result;
580  }
581 
582  int pos = 0, foldBegin = 0, foldMid = 0, foldEnd = 0;
583  while ( ( foldMid = header.indexOf( '\n', pos ) ) >= 0 ) {
584  foldBegin = foldEnd = foldMid;
585  // find the first space before the line-break
586  while ( foldBegin > 0 ) {
587  if ( !QChar::fromLatin1( header[foldBegin - 1] ).isSpace() ) {
588  break;
589  }
590  --foldBegin;
591  }
592  // find the first non-space after the line-break
593  while ( foldEnd <= header.length() - 1 ) {
594  if ( QChar::fromLatin1( header[foldEnd] ).isSpace() ) {
595  ++foldEnd;
596  } else if ( foldEnd > 0 && header[foldEnd - 1] == '\n' &&
597  header[foldEnd] == '=' && foldEnd + 2 < header.length() &&
598  ( ( header[foldEnd + 1] == '0' &&
599  header[foldEnd + 2] == '9' ) ||
600  ( header[foldEnd + 1] == '2' &&
601  header[foldEnd + 2] == '0' ) ) ) {
602  // bug #86302: malformed header continuation starting with =09/=20
603  foldEnd += 3;
604  }
605  else {
606  break;
607  }
608  }
609 
610  result += header.mid( pos, foldBegin - pos );
611  if ( foldEnd < header.length() - 1 ) {
612  result += ' ';
613  }
614  pos = foldEnd;
615  }
616  const int len = header.length();
617  if ( len > pos ) {
618  result += header.mid( pos, len - pos );
619  }
620  return result;
621 }
622 
623 int findHeaderLineEnd( const QByteArray &src, int &dataBegin, bool *folded )
624 {
625  int end = dataBegin;
626  int len = src.length() - 1;
627 
628  if ( folded ) {
629  *folded = false;
630  }
631 
632  if ( dataBegin < 0 ) {
633  // Not found
634  return -1;
635  }
636 
637  if ( dataBegin > len ) {
638  // No data available
639  return len + 1;
640  }
641 
642  // If the first line contains nothing, but the next line starts with a space
643  // or a tab, that means a stupid mail client has made the first header field line
644  // entirely empty, and has folded the rest to the next line(s).
645  if ( src.at( end ) == '\n' && end + 1 < len &&
646  ( src[end + 1] == ' ' || src[end + 1] == '\t' ) ) {
647 
648  // Skip \n and first whitespace
649  dataBegin += 2;
650  end += 2;
651  }
652 
653  if ( src.at( end ) != '\n' ) { // check if the header is not empty
654  while ( true ) {
655  end = src.indexOf( '\n', end + 1 );
656  if ( end == -1 || end == len ) {
657  // end of string
658  break;
659  } else if ( src[end + 1] == ' ' || src[end + 1] == '\t' ||
660  ( src[end + 1] == '=' && end + 3 <= len &&
661  ( ( src[end + 2] == '0' && src[end + 3] == '9' ) ||
662  ( src[end + 2] == '2' && src[end + 3] == '0' ) ) ) ) {
663  // next line is header continuation or starts with =09/=20 (bug #86302)
664  if ( folded ) {
665  *folded = true;
666  }
667  } else {
668  // end of header (no header continuation)
669  break;
670  }
671  }
672  }
673 
674  if ( end < 0 ) {
675  end = len + 1; //take the rest of the string
676  }
677  return end;
678 }
679 
680 int indexOfHeader( const QByteArray &src, const QByteArray &name, int &end, int &dataBegin, bool *folded )
681 {
682  QByteArray n = name;
683  n.append( ':' );
684  int begin = -1;
685 
686  if ( qstrnicmp( n.constData(), src.constData(), n.length() ) == 0 ) {
687  begin = 0;
688  } else {
689  n.prepend( '\n' );
690  const char *p = strcasestr( src.constData(), n.constData() );
691  if ( !p ) {
692  begin = -1;
693  } else {
694  begin = p - src.constData();
695  ++begin;
696  }
697  }
698 
699  if ( begin > -1 ) { //there is a header with the given name
700  dataBegin = begin + name.length() + 1; //skip the name
701  // skip the usual space after the colon
702  if ( src.at( dataBegin ) == ' ' ) {
703  ++dataBegin;
704  }
705  end = findHeaderLineEnd( src, dataBegin, folded );
706  return begin;
707 
708  } else {
709  end = -1;
710  dataBegin = -1;
711  return -1; //header not found
712  }
713 }
714 
715 QByteArray extractHeader( const QByteArray &src, const QByteArray &name )
716 {
717  int begin, end;
718  bool folded;
719  QByteArray result;
720 
721  if ( src.isEmpty() || indexOfHeader( src, name, end, begin, &folded ) < 0 ) {
722  return result;
723  }
724 
725  if ( begin >= 0 ) {
726  if ( !folded ) {
727  result = src.mid( begin, end - begin );
728  } else {
729  if ( end > begin ) {
730  QByteArray hdrValue = src.mid( begin, end - begin );
731  result = unfoldHeader( hdrValue );
732  }
733  }
734  }
735  return result;
736 }
737 
738 QList<QByteArray> extractHeaders( const QByteArray &src, const QByteArray &name )
739 {
740  int begin, end;
741  bool folded;
742  QList<QByteArray> result;
743  QByteArray copySrc( src );
744 
745  if ( indexOfHeader( copySrc, name, end, begin, &folded ) < 0 ) {
746  return result;
747  }
748 
749  while ( begin >= 0 ) {
750  if ( !folded ) {
751  result.append( copySrc.mid( begin, end - begin ) );
752  } else {
753  QByteArray hdrValue = copySrc.mid( begin, end - begin );
754  result.append( unfoldHeader( hdrValue ) );
755  }
756 
757  // get the next one, a tiny bit ugly, but we don't want the previous to be found again...
758  copySrc = copySrc.mid( end );
759  if ( indexOfHeader( copySrc, name, end, begin, &folded ) < 0 ) {
760  break;
761  }
762  }
763  return result;
764 }
765 
766 void removeHeader( QByteArray &header, const QByteArray &name )
767 {
768  int begin, end, dummy;
769  begin = indexOfHeader( header, name, end, dummy );
770  if ( begin >= 0 ) {
771  header.remove( begin, end - begin + 1 );
772  }
773 }
774 
775 QByteArray CRLFtoLF( const QByteArray &s )
776 {
777  QByteArray ret = s;
778  ret.replace( "\r\n", "\n" );
779  return ret;
780 }
781 
782 QByteArray CRLFtoLF( const char *s )
783 {
784  QByteArray ret = s;
785  return CRLFtoLF( ret );
786 }
787 
788 QByteArray LFtoCRLF( const QByteArray &s )
789 {
790  QByteArray ret = s;
791  ret.replace( '\n', "\r\n" );
792  return ret;
793 }
794 
795 QByteArray LFtoCRLF( const char *s )
796 {
797  QByteArray ret = s;
798  return LFtoCRLF( ret );
799 }
800 
801 namespace {
802 template < typename StringType, typename CharType > void removeQuotesGeneric( StringType & str )
803 {
804  bool inQuote = false;
805  for ( int i = 0; i < str.length(); ++i ) {
806  if ( str[i] == CharType( '"' ) ) {
807  str.remove( i, 1 );
808  i--;
809  inQuote = !inQuote;
810  } else {
811  if ( inQuote && ( str[i] == CharType( '\\' ) ) ) {
812  str.remove( i, 1 );
813  }
814  }
815  }
816 }
817 }
818 
819 void removeQuots( QByteArray &str )
820 {
821  removeQuotesGeneric<QByteArray, char>( str );
822 }
823 
824 void removeQuots( QString &str )
825 {
826  removeQuotesGeneric<QString, QLatin1Char>( str );
827 }
828 
829 template<class StringType,class CharType,class CharConverterType,class StringConverterType,class ToString>
830 void addQuotes_impl( StringType &str, bool forceQuotes )
831 {
832  bool needsQuotes=false;
833  for ( int i=0; i < str.length(); i++ ) {
834  const CharType cur = str.at( i );
835  if ( QString( ToString( str ) ).contains( QRegExp( QLatin1String( "\"|\\\\|=|\\]|\\[|:|;|,|\\.|,|@|<|>|\\)|\\(" ) ) ) ) {
836  needsQuotes = true;
837  }
838  if ( cur == CharConverterType( '\\' ) || cur == CharConverterType( '\"' ) ) {
839  str.insert( i, CharConverterType( '\\' ) );
840  i++;
841  }
842  }
843 
844  if ( needsQuotes || forceQuotes ) {
845  str.insert( 0, CharConverterType( '\"' ) );
846  str.append( StringConverterType( "\"" ) );
847  }
848 }
849 
850 void addQuotes( QByteArray &str, bool forceQuotes )
851 {
852  addQuotes_impl<QByteArray, char, char, char*, QLatin1String>( str, forceQuotes );
853 }
854 
855 void addQuotes( QString &str, bool forceQuotes )
856 {
857  addQuotes_impl<QString, QChar, QLatin1Char, QLatin1String, QString>( str, forceQuotes );
858 }
859 
860 KMIME_EXPORT QString balanceBidiState( const QString &input )
861 {
862  const int LRO = 0x202D;
863  const int RLO = 0x202E;
864  const int LRE = 0x202A;
865  const int RLE = 0x202B;
866  const int PDF = 0x202C;
867 
868  QString result = input;
869 
870  int openDirChangers = 0;
871  int numPDFsRemoved = 0;
872  for ( int i = 0; i < input.length(); i++ ) {
873  const ushort &code = input.at( i ).unicode();
874  if ( code == LRO || code == RLO || code == LRE || code == RLE ) {
875  openDirChangers++;
876  } else if ( code == PDF ) {
877  if ( openDirChangers > 0 ) {
878  openDirChangers--;
879  } else {
880  // One PDF too much, remove it
881  kWarning() << "Possible Unicode spoofing (unexpected PDF) detected in" << input;
882  result.remove( i - numPDFsRemoved, 1 );
883  numPDFsRemoved++;
884  }
885  }
886  }
887 
888  if ( openDirChangers > 0 ) {
889  kWarning() << "Possible Unicode spoofing detected in" << input;
890 
891  // At PDF chars to the end until the correct state is restored.
892  // As a special exception, when encountering quoted strings, place the PDF before
893  // the last quote.
894  for ( int i = openDirChangers; i > 0; i-- ) {
895  if ( result.endsWith( QLatin1Char( '"' ) ) ) {
896  result.insert( result.length() - 1, QChar( PDF ) );
897  } else {
898  result += QChar( PDF );
899  }
900  }
901  }
902 
903  return result;
904 }
905 
906 QString removeBidiControlChars( const QString &input )
907 {
908  const int LRO = 0x202D;
909  const int RLO = 0x202E;
910  const int LRE = 0x202A;
911  const int RLE = 0x202B;
912  QString result = input;
913  result.remove( LRO );
914  result.remove( RLO );
915  result.remove( LRE );
916  result.remove( RLE );
917  return result;
918 }
919 
920 static bool isCryptoPart( Content* content )
921 {
922  if ( !content->contentType( false ) ) {
923  return false;
924  }
925 
926  if ( content->contentType()->subType().toLower() == "octet-stream" &&
927  !content->contentDisposition( false ) ) {
928  return false;
929  }
930 
931  const Headers::ContentType *contentType = content->contentType();
932  const QByteArray lowerSubType = contentType->subType().toLower();
933  return ( contentType->mediaType().toLower() == "application" &&
934  ( lowerSubType == "pgp-encrypted" ||
935  lowerSubType == "pgp-signature" ||
936  lowerSubType == "pkcs7-mime" ||
937  lowerSubType == "x-pkcs7-mime" ||
938  lowerSubType == "pkcs7-signature" ||
939  lowerSubType == "x-pkcs7-signature" ||
940  ( lowerSubType == "octet-stream" &&
941  content->contentDisposition()->filename().toLower() == QLatin1String( "msg.asc" ) ) ) );
942 }
943 
944 bool hasAttachment( Content* content )
945 {
946  if ( !content ) {
947  return false;
948  }
949 
950  bool emptyFilename = true;
951  if ( content->contentDisposition( false ) &&
952  !content->contentDisposition()->filename().isEmpty() ) {
953  emptyFilename = false;
954  }
955 
956  if ( emptyFilename &&
957  content->contentType( false ) &&
958  !content->contentType()->name().isEmpty() ) {
959  emptyFilename = false;
960  }
961 
962  // ignore crypto parts
963  if ( !emptyFilename && !isCryptoPart( content ) ) {
964  return true;
965  }
966 
967  // Ok, content itself is not an attachment. now we deal with multiparts
968  if ( content->contentType()->isMultipart() ) {
969  Q_FOREACH ( Content *child, content->contents() ) {
970  if ( hasAttachment( child ) ) {
971  return true;
972  }
973  }
974  }
975  return false;
976 }
977 
978 bool hasInvitation( Content *content )
979 {
980  if ( !content ) {
981  return false;
982  }
983 
984  if ( isInvitation(content) ) {
985  return true;
986  }
987 
988  // Ok, content itself is not an invitation. now we deal with multiparts
989  if ( content->contentType()->isMultipart() ) {
990  Q_FOREACH ( Content *child, content->contents() ) {
991  if ( hasInvitation( child ) ) {
992  return true;
993  }
994  }
995  }
996  return false;
997 }
998 
999 bool isSigned( Message *message )
1000 {
1001  if ( !message ) {
1002  return false;
1003  }
1004 
1005  const KMime::Headers::ContentType* const contentType = message->contentType();
1006  if ( contentType->isSubtype( "signed" ) ||
1007  contentType->isSubtype( "pgp-signature" ) ||
1008  contentType->isSubtype( "pkcs7-signature" ) ||
1009  contentType->isSubtype( "x-pkcs7-signature" ) ||
1010  message->mainBodyPart( "multipart/signed" ) ||
1011  message->mainBodyPart( "application/pgp-signature" ) ||
1012  message->mainBodyPart( "application/pkcs7-signature" ) ||
1013  message->mainBodyPart( "application/x-pkcs7-signature" ) ) {
1014  return true;
1015  }
1016  return false;
1017 }
1018 
1019 bool isEncrypted( Message *message )
1020 {
1021  if ( !message ) {
1022  return false;
1023  }
1024 
1025  const KMime::Headers::ContentType* const contentType = message->contentType();
1026  if ( contentType->isSubtype( "encrypted" ) ||
1027  contentType->isSubtype( "pgp-encrypted" ) ||
1028  contentType->isSubtype( "pkcs7-mime" ) ||
1029  contentType->isSubtype( "x-pkcs7-mime" ) ||
1030  message->mainBodyPart( "multipart/encrypted" ) ||
1031  message->mainBodyPart( "application/pgp-encrypted" ) ||
1032  message->mainBodyPart( "application/pkcs7-mime" ) ||
1033  message->mainBodyPart( "application/x-pkcs7-mime" ) ) {
1034  return true;
1035  }
1036 
1037  return false;
1038 }
1039 
1040 bool isInvitation( Content *content )
1041 {
1042  if ( !content ) {
1043  return false;
1044  }
1045 
1046  const KMime::Headers::ContentType* const contentType = content->contentType( false );
1047 
1048  if ( contentType && contentType->isMediatype( "text" ) && contentType->isSubtype( "calendar" ) ) {
1049  return true;
1050  }
1051 
1052  return false;
1053 }
1054 
1055 } // namespace KMime
kmime_codecs.h
This file is part of the API for handling MIME data and defines the Codec class.
KMime::CharFreq::EightBitData
8bit binary
Definition: kmime_charfreq.h:103
QTextCodec::fromUnicode
QByteArray fromUnicode(const QString &str) const
KMime::CharFreq::SevenBitData
7bit binary
Definition: kmime_charfreq.h:105
KMime::CharFreq::EightBitText
8bit text
Definition: kmime_charfreq.h:106
QString::constData
const QChar * constData() const
QByteArray::clear
void clear()
KMime::Headers::ContentType::subType
QByteArray subType() const
Returns the mime sub-type (second part of the mimetype).
Definition: kmime_headers.cpp:1760
KMime::Headers::ContentType::isMultipart
bool isMultipart() const
Returns true if the associated MIME entity is a mulitpart container.
Definition: kmime_headers.cpp:1824
QTextCodec::name
virtual QByteArray name() const =0
KMime::Content::contents
List contents() const
For multipart contents, this will return a list of all multipart child contents.
Definition: kmime_content.cpp:532
QByteArray::toLower
QByteArray toLower() const
QByteArray::reserve
void reserve(int size)
QByteArray
QByteArray::at
char at(int i) const
QByteArray::setNum
QByteArray & setNum(short n, int base)
QByteArray::lastIndexOf
int lastIndexOf(char ch, int from) const
QChar
QByteArray::toUpper
QByteArray toUpper() const
QByteArray::isEmpty
bool isEmpty() const
KMime::Message::mainBodyPart
Content * mainBodyPart(const QByteArray &type=QByteArray())
Returns the first main body part of a given type, taking multipart/mixed and multipart/alternative no...
Definition: kmime_message.cpp:103
KMime::Content::contentDisposition
Headers::ContentDisposition * contentDisposition(bool create=true)
Returns the Content-Disposition header.
QByteArray::length
int length() const
QString::remove
QString & remove(int position, int n)
QTextCodec::ConverterState
KMime::Headers::ContentType::isSubtype
bool isSubtype(const char *subtype) const
Tests if the mime sub-type equals subtype.
Definition: kmime_headers.cpp:1792
KMime::CharFreq::None
Unknown.
Definition: kmime_charfreq.h:102
QRegExp
QByteArray::indexOf
int indexOf(char ch, int from) const
kmime_charfreq.h
This file is part of the API for handling MIME data and defines the CharFreq class.
QList::append
void append(const T &value)
QString::fromUtf8
QString fromUtf8(const char *str, int size)
QChar::isSpace
bool isSpace() const
QString::insert
QString & insert(int position, QChar ch)
QChar::fromLatin1
QChar fromLatin1(char c)
QByteArray::prepend
QByteArray & prepend(char ch)
QString::isEmpty
bool isEmpty() const
QByteArray::constData
const char * constData() const
QByteArray::right
QByteArray right(int len) const
QByteArray::replace
QByteArray & replace(int pos, int len, const char *after)
QString::endsWith
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const
QString
QList< QByteArray >
QChar::unicode
ushort unicode() const
QTextCodec
QByteArray::mid
QByteArray mid(int pos, int len) const
KMime::Content::contentType
Headers::ContentType * contentType(bool create=true)
Returns the Content-Type header.
QByteArray::append
QByteArray & append(char ch)
QString::toLower
QString toLower() const
QString::toLocal8Bit
QByteArray toLocal8Bit() const
QString::contains
bool contains(QChar ch, Qt::CaseSensitivity cs) const
QLatin1Char
QChar::toLatin1
char toLatin1() const
KMime::Headers::ContentType::name
QString name() const
Returns the name of the associated MIME entity.
Definition: kmime_headers.cpp:1859
QByteArray::left
QByteArray left(int len) const
KMime::Message
Represents a (email) message.
Definition: kmime_message.h:81
QString::toLatin1
QByteArray toLatin1() const
QString::mid
QString mid(int position, int n) const
QLatin1String
QString::at
const QChar at(int position) const
QTextCodec::codecForName
QTextCodec * codecForName(const QByteArray &name)
KMime::Headers::ContentType::isMediatype
bool isMediatype(const char *mediatype) const
Tests if the media type equals mediatype.
Definition: kmime_headers.cpp:1784
QList::last
T & last()
QByteArray::contains
bool contains(char ch) const
QString::length
int length() const
KMime::Content
A class that encapsulates MIME encoded Content.
Definition: kmime_content.h:112
QByteArray::data
char * data()
QString::fromLatin1
QString fromLatin1(const char *str, int size)
KMime::Headers::ContentDisposition::filename
QString filename() const
Returns the suggested filename for the associated MIME part.
Definition: kmime_headers.cpp:2201
QByteArray::toBase64
QByteArray toBase64() const
KMime::Headers::ContentType::mediaType
QByteArray mediaType() const
Returns the media type (first part of the mimetype).
Definition: kmime_headers.cpp:1749
KMime::CharFreq
A class for performing basic data typing using frequency count heuristics.
Definition: kmime_charfreq.h:78
QByteArray::remove
QByteArray & remove(int pos, int len)
KMime::Headers::ContentType
Represents a "Content-Type" header.
Definition: kmime_headers.h:1031
KMime::CharFreq::SevenBitText
7bit text
Definition: kmime_charfreq.h:107
QTextCodec::toUnicode
QString toUnicode(const QByteArray &a) const
QString::toUtf8
QByteArray toUtf8() const
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jun 22 2020 13:37:18 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KMIME Library

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

kdepimlibs API Reference

Skip menu "kdepimlibs API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2

Search



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