/* kmime_util.cpp KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kmime_util.h" #include "kmime_util_p.h" #include "kmime_charfreq.h" #include "kmime_codecs.h" #include "kmime_header_parsing.h" #include "kmime_message.h" #include "kmime_warning.h" #include #include // for strcasestr #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KMime; namespace KMime { QList c_harsetCache; QList l_anguageCache; QString f_allbackCharEnc; bool u_seOutlookEncoding = false; QByteArray cachedCharset( const QByteArray &name ) { foreach ( const QByteArray& charset, c_harsetCache ) { if ( qstricmp( name.data(), charset.data() ) == 0 ) { return charset; } } c_harsetCache.append( name.toUpper() ); //kDebug() << "KNMimeBase::cachedCharset() number of cs" << c_harsetCache.count(); return c_harsetCache.last(); } QByteArray cachedLanguage( const QByteArray &name ) { foreach ( const QByteArray& language, l_anguageCache ) { if ( qstricmp( name.data(), language.data() ) == 0 ) { return language; } } l_anguageCache.append( name.toUpper() ); //kDebug() << "KNMimeBase::cachedCharset() number of cs" << c_harsetCache.count(); return l_anguageCache.last(); } bool isUsAscii( const QString &s ) { uint sLength = s.length(); for ( uint i=0; i encodingsForData( const QByteArray &data ) { QList allowed; CharFreq cf( data ); switch ( cf.type() ) { case CharFreq::SevenBitText: allowed << Headers::CE7Bit; case CharFreq::EightBitText: allowed << Headers::CE8Bit; case CharFreq::SevenBitData: if ( cf.printableRatio() > 5.0/6.0 ) { // let n the length of data and p the number of printable chars. // Then base64 \approx 4n/3; qp \approx p + 3(n-p) // => qp < base64 iff p > 5n/6. allowed << Headers::CEquPr; allowed << Headers::CEbase64; } else { allowed << Headers::CEbase64; allowed << Headers::CEquPr; } break; case CharFreq::EightBitData: allowed << Headers::CEbase64; break; case CharFreq::None: default: Q_ASSERT( false ); } return allowed; } // "(),.:;<>@[\] const uchar specialsMap[16] = { 0x00, 0x00, 0x00, 0x00, // CTLs 0x20, 0xCA, 0x00, 0x3A, // SPACE ... '?' 0x80, 0x00, 0x00, 0x1C, // '@' ... '_' 0x00, 0x00, 0x00, 0x00 // '`' ... DEL }; // "(),:;<>@[\]/=? const uchar tSpecialsMap[16] = { 0x00, 0x00, 0x00, 0x00, // CTLs 0x20, 0xC9, 0x00, 0x3F, // SPACE ... '?' 0x80, 0x00, 0x00, 0x1C, // '@' ... '_' 0x00, 0x00, 0x00, 0x00 // '`' ... DEL }; // all except specials, CTLs, SPACE. const uchar aTextMap[16] = { 0x00, 0x00, 0x00, 0x00, 0x5F, 0x35, 0xFF, 0xC5, 0x7F, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFE }; // all except tspecials, CTLs, SPACE. const uchar tTextMap[16] = { 0x00, 0x00, 0x00, 0x00, 0x5F, 0x36, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFE }; // none except a-zA-Z0-9!*+-/ const uchar eTextMap[16] = { 0x00, 0x00, 0x00, 0x00, 0x40, 0x35, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xFF, 0xE0 }; void setFallbackCharEncoding(const QString& fallbackCharEnc) { f_allbackCharEnc = fallbackCharEnc; } QString fallbackCharEncoding() { return f_allbackCharEnc; } void setUseOutlookAttachmentEncoding( bool violateStandard ) { u_seOutlookEncoding = violateStandard; } bool useOutlookAttachmentEncoding() { return u_seOutlookEncoding; } QString decodeRFC2047String( const QByteArray &src, QByteArray &usedCS, const QByteArray &defaultCS, bool forceCS ) { QByteArray result; QByteArray spaceBuffer; const char *scursor = src.constData(); const char *send = scursor + src.length(); bool onlySpacesSinceLastWord = false; while ( scursor != send ) { // space if ( isspace( *scursor ) && onlySpacesSinceLastWord ) { spaceBuffer += *scursor++; continue; } // possible start of an encoded word if ( *scursor == '=' ) { QByteArray language; QString decoded; ++scursor; const char *start = scursor; if ( HeaderParsing::parseEncodedWord( scursor, send, decoded, language, usedCS, defaultCS, forceCS ) ) { result += decoded.toUtf8(); onlySpacesSinceLastWord = true; spaceBuffer.clear(); } else { if ( onlySpacesSinceLastWord ) { result += spaceBuffer; onlySpacesSinceLastWord = false; } result += '='; scursor = start; // reset cursor after parsing failure } continue; } else { // unencoded data if ( onlySpacesSinceLastWord ) { result += spaceBuffer; onlySpacesSinceLastWord = false; } result += *scursor; ++scursor; } } // If there are any chars that couldn't be decoded in UTF-8, // use the fallback charset if it exists const QString tryUtf8 = QString::fromUtf8( result ); if ( tryUtf8.contains( 0xFFFD ) && !f_allbackCharEnc.isEmpty() ) { QTextCodec* codec = KGlobal::charsets()->codecForName( f_allbackCharEnc ); return codec->toUnicode( result ); } else { return tryUtf8; } } QString decodeRFC2047String( const QByteArray &src ) { QByteArray usedCS; return decodeRFC2047String( src, usedCS, "utf-8", false ); } static const char *reservedCharacters = "\"()<>@,.;:\\[]="; QByteArray encodeRFC2047String( const QString &src, const QByteArray &charset, bool addressHeader, bool allow8BitHeaders ) { QByteArray result; int start=0, end=0; bool nonAscii=false, ok=true, useQEncoding=false; // fromLatin1() is safe here, codecForName() uses toLatin1() internally const QTextCodec *codec = KGlobal::charsets()->codecForName( QString::fromLatin1( charset ), ok ); QByteArray usedCS; if ( !ok ) { //no codec available => try local8Bit and hope the best ;-) usedCS = KGlobal::locale()->encoding(); codec = KGlobal::charsets()->codecForName( QString::fromLatin1( usedCS ), ok ); } else { Q_ASSERT( codec ); if ( charset.isEmpty() ) { usedCS = codec->name(); } else { usedCS = charset; } } QTextCodec::ConverterState converterState( QTextCodec::IgnoreHeader ); QByteArray encoded8Bit = codec->fromUnicode( src.constData(), src.length(), &converterState ); if ( converterState.invalidChars > 0 ) { usedCS = "utf-8"; codec = QTextCodec::codecForName( usedCS ); encoded8Bit = codec->fromUnicode( src ); } if ( usedCS.contains( "8859-" ) ) { // use "B"-Encoding for non iso-8859-x charsets useQEncoding = true; } if ( allow8BitHeaders ) { return encoded8Bit; } uint encoded8BitLength = encoded8Bit.length(); for ( unsigned int i=0; i@,.;:\\[]=", encoded8Bit[i] ) != 0 ) ) ) { end = start; // non us-ascii char found, now we determine where to stop encoding nonAscii = true; break; } } if ( nonAscii ) { while ( ( end < encoded8Bit.length() ) && ( encoded8Bit[end] != ' ' ) ) { // we encode complete words end++; } for ( int x=end; x= 'a' ) && ( c <= 'z' ) ) || // paranoid mode, encode *all* special chars to avoid problems ( ( c >= 'A' ) && ( c <= 'Z' ) ) || // with "From" & "To" headers ( ( c >= '0' ) && ( c <= '9' ) ) ) { result += c; } else { result += '='; // "stolen" from KMail ;-) hexcode = ( ( c & 0xF0 ) >> 4 ) + 48; if ( hexcode >= 58 ) { hexcode += 7; } result += hexcode; hexcode = ( c & 0x0F ) + 48; if ( hexcode >= 58 ) { hexcode += 7; } result += hexcode; } } } } else { result += "?B?" + encoded8Bit.mid( start, end - start ).toBase64(); } result +="?="; result += encoded8Bit.right( encoded8Bit.length() - end ); } else { result = encoded8Bit; } return result; } QByteArray encodeRFC2047Sentence(const QString& src, const QByteArray& charset ) { QByteArray result; QList splitChars; splitChars << QLatin1Char( ',' ) << QLatin1Char( '\"' ) << QLatin1Char( ';' ) << QLatin1Char( '\\' ); const QChar *ch = src.constData(); const int length = src.length(); int pos = 0; int wordStart = 0; //qDebug() << "Input:" << src; // Loop over all characters of the string. // When encountering a split character, RFC-2047-encode the word before it, and add it to the result. while ( pos < length ) { //qDebug() << "Pos:" << pos << "Result:" << result << "Char:" << ch->toLatin1(); const bool isAscii = ch->unicode() < 127; const bool isReserved = ( strchr( reservedCharacters, ch->toLatin1() ) != 0 ); if ( isAscii && isReserved ) { const int wordSize = pos - wordStart; if ( wordSize > 0 ) { const QString word = src.mid( wordStart, wordSize ); result += encodeRFC2047String( word, charset ); } result += ch->toLatin1(); wordStart = pos + 1; } ch++; pos++; } // Encode the last word const int wordSize = pos - wordStart; if ( wordSize > 0 ) { const QString word = src.mid( wordStart, pos - wordStart ); result += encodeRFC2047String( word, charset ); } return result; } //----------------------------------------------------------------------------- QByteArray encodeRFC2231String( const QString& str, const QByteArray& charset ) { if ( str.isEmpty() ) { return QByteArray(); } const QTextCodec *codec = KGlobal::charsets()->codecForName( QString::fromLatin1( charset ) ); QByteArray latin; if ( charset == "us-ascii" ) { latin = str.toLatin1(); } else if ( codec ) { latin = codec->fromUnicode( str ); } else { latin = str.toLocal8Bit(); } char *l; for ( l = latin.data(); *l; ++l ) { if ( ( ( *l & 0xE0 ) == 0 ) || ( *l & 0x80 ) ) { // *l is control character or 8-bit char break; } } if ( !*l ) { return latin; } QByteArray result = charset + "''"; for ( l = latin.data(); *l; ++l ) { bool needsQuoting = ( *l & 0x80 ) || ( *l == '%' ); if ( !needsQuoting ) { const QByteArray especials = "()<>@,;:\"/[]?.= \033"; int len = especials.length(); for ( int i = 0; i < len; i++ ) { if ( *l == especials[i] ) { needsQuoting = true; break; } } } if ( needsQuoting ) { result += '%'; unsigned char hexcode; hexcode = ( ( *l & 0xF0 ) >> 4 ) + 48; if ( hexcode >= 58 ) { hexcode += 7; } result += hexcode; hexcode = ( *l & 0x0F ) + 48; if ( hexcode >= 58 ) { hexcode += 7; } result += hexcode; } else { result += *l; } } return result; } //----------------------------------------------------------------------------- QString decodeRFC2231String( const QByteArray &str, QByteArray &usedCS, const QByteArray &defaultCS, bool forceCS ) { int p = str.indexOf( '\'' ); if ( p < 0 ) { return KGlobal::charsets()->codecForName( QString::fromLatin1( defaultCS ) )->toUnicode( str ); } QByteArray charset = str.left( p ); QByteArray st = str.mid( str.lastIndexOf( '\'' ) + 1 ); char ch, ch2; p = 0; while ( p < (int)st.length() ) { if ( st.at( p ) == 37 ) { // Only try to decode the percent-encoded character if the percent sign // is really followed by two other characters, see testcase at bug 163024 if ( p + 2 < st.length() ) { ch = st.at( p + 1 ) - 48; if ( ch > 16 ) { ch -= 7; } ch2 = st.at( p + 2 ) - 48; if ( ch2 > 16 ) { ch2 -= 7; } st[p] = ch * 16 + ch2; st.remove( p + 1, 2 ); } } p++; } kDebug() << "Got pre-decoded:" << st; QString result; const QTextCodec * charsetcodec = KGlobal::charsets()->codecForName( QString::fromLatin1( charset ) ); if ( !charsetcodec || forceCS ) { charsetcodec = KGlobal::charsets()->codecForName( QString::fromLatin1( defaultCS ) ); } usedCS = charsetcodec->name(); return charsetcodec->toUnicode( st ); } QString decodeRFC2231String( const QByteArray &src ) { QByteArray usedCS; return decodeRFC2231String( src, usedCS, "utf-8", false ); } QByteArray uniqueString() { static char chars[] = "0123456789abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; time_t now; char p[11]; int pos, ran; unsigned int timeval; p[10] = '\0'; now = time( 0 ); ran = 1 + (int)( 1000.0 * rand() / ( RAND_MAX + 1.0 ) ); timeval = ( now / ran ) + getpid(); for ( int i = 0; i < 10; i++ ) { pos = (int) ( 61.0 * rand() / ( RAND_MAX + 1.0 ) ); //kDebug() << pos; p[i] = chars[pos]; } QByteArray ret; ret.setNum( timeval ); ret += '.'; ret += p; return ret; } QByteArray multiPartBoundary() { return "nextPart" + uniqueString(); } QByteArray unfoldHeader( const QByteArray &header ) { QByteArray result; if ( header.isEmpty() ) { return result; } int pos = 0, foldBegin = 0, foldMid = 0, foldEnd = 0; while ( ( foldMid = header.indexOf( '\n', pos ) ) >= 0 ) { foldBegin = foldEnd = foldMid; // find the first space before the line-break while ( foldBegin > 0 ) { if ( !QChar::fromLatin1( header[foldBegin - 1] ).isSpace() ) { break; } --foldBegin; } // find the first non-space after the line-break while ( foldEnd <= header.length() - 1 ) { if ( QChar::fromLatin1( header[foldEnd] ).isSpace() ) { ++foldEnd; } else if ( foldEnd > 0 && header[foldEnd - 1] == '\n' && header[foldEnd] == '=' && foldEnd + 2 < header.length() && ( ( header[foldEnd + 1] == '0' && header[foldEnd + 2] == '9' ) || ( header[foldEnd + 1] == '2' && header[foldEnd + 2] == '0' ) ) ) { // bug #86302: malformed header continuation starting with =09/=20 foldEnd += 3; } else { break; } } result += header.mid( pos, foldBegin - pos ); if ( foldEnd < header.length() - 1 ) { result += ' '; } pos = foldEnd; } const int len = header.length(); if ( len > pos ) { result += header.mid( pos, len - pos ); } return result; } int findHeaderLineEnd( const QByteArray &src, int &dataBegin, bool *folded ) { int end = dataBegin; int len = src.length() - 1; if ( folded ) { *folded = false; } if ( dataBegin < 0 ) { // Not found return -1; } if ( dataBegin > len ) { // No data available return len + 1; } // If the first line contains nothing, but the next line starts with a space // or a tab, that means a stupid mail client has made the first header field line // entirely empty, and has folded the rest to the next line(s). if ( src.at( end ) == '\n' && end + 1 < len && ( src[end + 1] == ' ' || src[end + 1] == '\t' ) ) { // Skip \n and first whitespace dataBegin += 2; end += 2; } if ( src.at( end ) != '\n' ) { // check if the header is not empty while ( true ) { end = src.indexOf( '\n', end + 1 ); if ( end == -1 || end == len ) { // end of string break; } else if ( src[end + 1] == ' ' || src[end + 1] == '\t' || ( src[end + 1] == '=' && end + 3 <= len && ( ( src[end + 2] == '0' && src[end + 3] == '9' ) || ( src[end + 2] == '2' && src[end + 3] == '0' ) ) ) ) { // next line is header continuation or starts with =09/=20 (bug #86302) if ( folded ) { *folded = true; } } else { // end of header (no header continuation) break; } } } if ( end < 0 ) { end = len + 1; //take the rest of the string } return end; } int indexOfHeader( const QByteArray &src, const QByteArray &name, int &end, int &dataBegin, bool *folded ) { QByteArray n = name; n.append( ':' ); int begin = -1; if ( qstrnicmp( n.constData(), src.constData(), n.length() ) == 0 ) { begin = 0; } else { n.prepend( '\n' ); const char *p = strcasestr( src.constData(), n.constData() ); if ( !p ) { begin = -1; } else { begin = p - src.constData(); ++begin; } } if ( begin > -1 ) { //there is a header with the given name dataBegin = begin + name.length() + 1; //skip the name // skip the usual space after the colon if ( src.at( dataBegin ) == ' ' ) { ++dataBegin; } end = findHeaderLineEnd( src, dataBegin, folded ); return begin; } else { end = -1; dataBegin = -1; return -1; //header not found } } QByteArray extractHeader( const QByteArray &src, const QByteArray &name ) { int begin, end; bool folded; QByteArray result; if ( src.isEmpty() || indexOfHeader( src, name, end, begin, &folded ) < 0 ) { return result; } if ( begin >= 0 ) { if ( !folded ) { result = src.mid( begin, end - begin ); } else { if ( end > begin ) { QByteArray hdrValue = src.mid( begin, end - begin ); result = unfoldHeader( hdrValue ); } } } return result; } QList extractHeaders( const QByteArray &src, const QByteArray &name ) { int begin, end; bool folded; QList result; QByteArray copySrc( src ); if ( indexOfHeader( copySrc, name, end, begin, &folded ) < 0 ) { return result; } while ( begin >= 0 ) { if ( !folded ) { result.append( copySrc.mid( begin, end - begin ) ); } else { QByteArray hdrValue = copySrc.mid( begin, end - begin ); result.append( unfoldHeader( hdrValue ) ); } // get the next one, a tiny bit ugly, but we don't want the previous to be found again... copySrc = copySrc.mid( end ); if ( indexOfHeader( copySrc, name, end, begin, &folded ) < 0 ) { break; } } return result; } void removeHeader( QByteArray &header, const QByteArray &name ) { int begin, end, dummy; begin = indexOfHeader( header, name, end, dummy ); if ( begin >= 0 ) { header.remove( begin, end - begin + 1 ); } } QByteArray CRLFtoLF( const QByteArray &s ) { QByteArray ret = s; ret.replace( "\r\n", "\n" ); return ret; } QByteArray CRLFtoLF( const char *s ) { QByteArray ret = s; return CRLFtoLF( ret ); } QByteArray LFtoCRLF( const QByteArray &s ) { QByteArray ret = s; ret.replace( '\n', "\r\n" ); return ret; } QByteArray LFtoCRLF( const char *s ) { QByteArray ret = s; return LFtoCRLF( ret ); } namespace { template < typename StringType, typename CharType > void removeQuotesGeneric( StringType & str ) { bool inQuote = false; for ( int i = 0; i < str.length(); ++i ) { if ( str[i] == CharType( '"' ) ) { str.remove( i, 1 ); i--; inQuote = !inQuote; } else { if ( inQuote && ( str[i] == CharType( '\\' ) ) ) { str.remove( i, 1 ); } } } } } void removeQuots( QByteArray &str ) { removeQuotesGeneric( str ); } void removeQuots( QString &str ) { removeQuotesGeneric( str ); } template void addQuotes_impl( StringType &str, bool forceQuotes ) { bool needsQuotes=false; for ( int i=0; i < str.length(); i++ ) { const CharType cur = str.at( i ); if ( QString( ToString( str ) ).contains( QRegExp( QLatin1String( "\"|\\\\|=|\\]|\\[|:|;|,|\\.|,|@|<|>|\\)|\\(" ) ) ) ) { needsQuotes = true; } if ( cur == CharConverterType( '\\' ) || cur == CharConverterType( '\"' ) ) { str.insert( i, CharConverterType( '\\' ) ); i++; } } if ( needsQuotes || forceQuotes ) { str.insert( 0, CharConverterType( '\"' ) ); str.append( StringConverterType( "\"" ) ); } } void addQuotes( QByteArray &str, bool forceQuotes ) { addQuotes_impl( str, forceQuotes ); } void addQuotes( QString &str, bool forceQuotes ) { addQuotes_impl( str, forceQuotes ); } KMIME_EXPORT QString balanceBidiState( const QString &input ) { const int LRO = 0x202D; const int RLO = 0x202E; const int LRE = 0x202A; const int RLE = 0x202B; const int PDF = 0x202C; QString result = input; int openDirChangers = 0; int numPDFsRemoved = 0; for ( int i = 0; i < input.length(); i++ ) { const ushort &code = input.at( i ).unicode(); if ( code == LRO || code == RLO || code == LRE || code == RLE ) { openDirChangers++; } else if ( code == PDF ) { if ( openDirChangers > 0 ) { openDirChangers--; } else { // One PDF too much, remove it kWarning() << "Possible Unicode spoofing (unexpected PDF) detected in" << input; result.remove( i - numPDFsRemoved, 1 ); numPDFsRemoved++; } } } if ( openDirChangers > 0 ) { kWarning() << "Possible Unicode spoofing detected in" << input; // At PDF chars to the end until the correct state is restored. // As a special exception, when encountering quoted strings, place the PDF before // the last quote. for ( int i = openDirChangers; i > 0; i-- ) { if ( result.endsWith( QLatin1Char( '"' ) ) ) { result.insert( result.length() - 1, QChar( PDF ) ); } else { result += QChar( PDF ); } } } return result; } QString removeBidiControlChars( const QString &input ) { const int LRO = 0x202D; const int RLO = 0x202E; const int LRE = 0x202A; const int RLE = 0x202B; QString result = input; result.remove( LRO ); result.remove( RLO ); result.remove( LRE ); result.remove( RLE ); return result; } static bool isCryptoPart( Content* content ) { if ( !content->contentType( false ) ) { return false; } if ( content->contentType()->subType().toLower() == "octet-stream" && !content->contentDisposition( false ) ) { return false; } const Headers::ContentType *contentType = content->contentType(); const QByteArray lowerSubType = contentType->subType().toLower(); return ( contentType->mediaType().toLower() == "application" && ( lowerSubType == "pgp-encrypted" || lowerSubType == "pgp-signature" || lowerSubType == "pkcs7-mime" || lowerSubType == "pkcs7-signature" || lowerSubType == "x-pkcs7-signature" || ( lowerSubType == "octet-stream" && content->contentDisposition()->filename().toLower() == QLatin1String( "msg.asc" ) ) ) ); } bool hasAttachment( Content* content ) { if ( !content ) { return false; } bool emptyFilename = true; if ( content->contentDisposition( false ) && !content->contentDisposition()->filename().isEmpty() ) { emptyFilename = false; } if ( emptyFilename && content->contentType( false ) && !content->contentType()->name().isEmpty() ) { emptyFilename = false; } // ignore crypto parts if ( !emptyFilename && !isCryptoPart( content ) ) { return true; } // Ok, content itself is not an attachment. now we deal with multiparts if ( content->contentType()->isMultipart() ) { Q_FOREACH ( Content *child, content->contents() ) { if ( hasAttachment( child ) ) { return true; } } } return false; } bool isSigned( Message *message ) { if ( !message ) { return false; } const KMime::Headers::ContentType* const contentType = message->contentType(); if ( contentType->isSubtype( "signed" ) || contentType->isSubtype( "pgp-signature" ) || contentType->isSubtype( "pkcs7-signature" ) || contentType->isSubtype( "x-pkcs7-signature" ) || message->mainBodyPart( "multipart/signed" ) || message->mainBodyPart( "application/pgp-signature" ) || message->mainBodyPart( "application/pkcs7-signature" ) || message->mainBodyPart( "application/x-pkcs7-signature" ) ) { return true; } return false; } bool isEncrypted( Message *message ) { if ( !message ) { return false; } const KMime::Headers::ContentType* const contentType = message->contentType(); if ( contentType->isSubtype( "encrypted" ) || contentType->isSubtype( "pgp-encrypted" ) || contentType->isSubtype( "pkcs7-mime" ) || message->mainBodyPart( "multipart/encrypted" ) || message->mainBodyPart( "application/pgp-encrypted" ) || message->mainBodyPart( "application/pkcs7-mime" ) ) { return true; } return false; } bool isInvitation( Content *content ) { if ( !content ) { return false; } const KMime::Headers::ContentType* const contentType = content->contentType( false ); if ( contentType && contentType->isMediatype( "text" ) && contentType->isSubtype( "calendar" ) ) { return true; } return false; } } // namespace KMime