• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.14.10 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 <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
51using namespace KMime;
52
53namespace KMime {
54
55QList<QByteArray> c_harsetCache;
56QList<QByteArray> l_anguageCache;
57QString f_allbackCharEnc;
58bool u_seOutlookEncoding = false;
59
60QByteArray 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
73QByteArray 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
86bool 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
97QString 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
110QList<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// "(),.:;<>@[\]
144const 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// "(),:;<>@[\]/=?
152const 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.
160const 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.
168const 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!*+-/
176const 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
183void setFallbackCharEncoding(const QString& fallbackCharEnc)
184{
185 f_allbackCharEnc = fallbackCharEnc;
186}
187
188QString fallbackCharEncoding()
189{
190 return f_allbackCharEnc;
191}
192
193void setUseOutlookAttachmentEncoding( bool violateStandard )
194{
195 u_seOutlookEncoding = violateStandard;
196}
197
198bool useOutlookAttachmentEncoding()
199{
200 return u_seOutlookEncoding;
201}
202
203
204QString 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
262QString decodeRFC2047String( const QByteArray &src )
263{
264 QByteArray usedCS;
265 return decodeRFC2047String( src, usedCS, "utf-8", false );
266}
267
268static const char *reservedCharacters = "\"()<>@,.;:\\[]=";
269
270QByteArray 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
386QByteArray 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//-----------------------------------------------------------------------------
430QByteArray 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//-----------------------------------------------------------------------------
492QString 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
537QString decodeRFC2231String( const QByteArray &src )
538{
539 QByteArray usedCS;
540 return decodeRFC2231String( src, usedCS, "utf-8", false );
541}
542
543QByteArray 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
570QByteArray multiPartBoundary()
571{
572 return "nextPart" + uniqueString();
573}
574
575QByteArray 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
623int 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
680int 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
715QByteArray 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
738QList<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
766void 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
775QByteArray CRLFtoLF( const QByteArray &s )
776{
777 QByteArray ret = s;
778 ret.replace( "\r\n", "\n" );
779 return ret;
780}
781
782QByteArray CRLFtoLF( const char *s )
783{
784 QByteArray ret = s;
785 return CRLFtoLF( ret );
786}
787
788QByteArray LFtoCRLF( const QByteArray &s )
789{
790 QByteArray ret = s;
791 ret.replace( '\n', "\r\n" );
792 return ret;
793}
794
795QByteArray LFtoCRLF( const char *s )
796{
797 QByteArray ret = s;
798 return LFtoCRLF( ret );
799}
800
801namespace {
802template < 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
819void removeQuots( QByteArray &str )
820{
821 removeQuotesGeneric<QByteArray, char>( str );
822}
823
824void removeQuots( QString &str )
825{
826 removeQuotesGeneric<QString, QLatin1Char>( str );
827}
828
829template<class StringType,class CharType,class CharConverterType,class StringConverterType,class ToString>
830void 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
850void addQuotes( QByteArray &str, bool forceQuotes )
851{
852 addQuotes_impl<QByteArray, char, char, char*, QLatin1String>( str, forceQuotes );
853}
854
855void addQuotes( QString &str, bool forceQuotes )
856{
857 addQuotes_impl<QString, QChar, QLatin1Char, QLatin1String, QString>( str, forceQuotes );
858}
859
860KMIME_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
906QString 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
920static 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
944bool 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
978bool 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
999bool 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
1019bool 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
1040bool 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::CharFreq
A class for performing basic data typing using frequency count heuristics.
Definition: kmime_charfreq.h:79
KMime::CharFreq::EightBitData
@ EightBitData
8bit binary
Definition: kmime_charfreq.h:103
KMime::CharFreq::None
@ None
Unknown.
Definition: kmime_charfreq.h:102
KMime::CharFreq::SevenBitData
@ SevenBitData
7bit binary
Definition: kmime_charfreq.h:105
KMime::CharFreq::EightBitText
@ EightBitText
8bit text
Definition: kmime_charfreq.h:106
KMime::CharFreq::SevenBitText
@ SevenBitText
7bit text
Definition: kmime_charfreq.h:107
KMime::Content
A class that encapsulates MIME encoded Content.
Definition: kmime_content.h:113
KMime::Content::contentType
Headers::ContentType * contentType(bool create=true)
Returns the Content-Type header.
KMime::Content::contentDisposition
Headers::ContentDisposition * contentDisposition(bool create=true)
Returns the Content-Disposition header.
KMime::Content::contents
List contents() const
For multipart contents, this will return a list of all multipart child contents.
Definition: kmime_content.cpp:532
KMime::Headers::ContentDisposition::filename
QString filename() const
Returns the suggested filename for the associated MIME part.
Definition: kmime_headers.cpp:2201
KMime::Headers::ContentType
Represents a "Content-Type" header.
Definition: kmime_headers.h:1032
KMime::Headers::ContentType::mediaType
QByteArray mediaType() const
Returns the media type (first part of the mimetype).
Definition: kmime_headers.cpp:1749
KMime::Headers::ContentType::isMediatype
bool isMediatype(const char *mediatype) const
Tests if the media type equals mediatype.
Definition: kmime_headers.cpp:1784
KMime::Headers::ContentType::isSubtype
bool isSubtype(const char *subtype) const
Tests if the mime sub-type equals subtype.
Definition: kmime_headers.cpp:1792
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::name
QString name() const
Returns the name of the associated MIME entity.
Definition: kmime_headers.cpp:1859
KMime::Headers::ContentType::isMultipart
bool isMultipart() const
Returns true if the associated MIME entity is a mulitpart container.
Definition: kmime_headers.cpp:1824
KMime::Message
Represents a (email) message.
Definition: kmime_message.h:82
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_charfreq.h
This file is part of the API for handling MIME data and defines the CharFreq class.
kmime_codecs.h
This file is part of the API for handling MIME data and defines the Codec class.
KMime::Headers::contentEncoding
contentEncoding
Various possible values for the "Content-Transfer-Encoding" header.
Definition: kmime_headers.h:74
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Jul 21 2022 00:00:00 by doxygen 1.9.5 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-4.14.10 API Reference

Skip menu "kdepimlibs-4.14.10 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
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