Warning, file /pim/kmime/src/kmime_util.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002   kmime_util.cpp
0003 
0004   KMime, the KDE Internet mail/usenet news message library.
0005   SPDX-FileCopyrightText: 2001 the KMime authors.
0006   See file AUTHORS for details
0007 
0008   SPDX-License-Identifier: LGPL-2.0-or-later
0009 */
0010 
0011 #include "kmime_util.h"
0012 #include "kmime_util_p.h"
0013 
0014 #include "kmime_charfreq.h"
0015 #include "kmime_debug.h"
0016 #include "kmime_header_parsing.h"
0017 #include "kmime_message.h"
0018 #include "kmime_warning.h"
0019 
0020 #include <config-kmime.h>
0021 
0022 #include <KCharsets>
0023 #include <QCoreApplication>
0024 #include <QRegularExpression>
0025 
0026 #include <cctype>
0027 #include <cstdlib>
0028 #include <ctime>
0029 
0030 using namespace KMime;
0031 
0032 namespace KMime
0033 {
0034 
0035 QVector<QByteArray> c_harsetCache;
0036 bool u_seOutlookEncoding = false;
0037 
0038 QByteArray cachedCharset(const QByteArray &name)
0039 {
0040     for (const QByteArray &charset : std::as_const(c_harsetCache)) {
0041         if (qstricmp(name.data(), charset.data()) == 0) {
0042             return charset;
0043         }
0044     }
0045 
0046     c_harsetCache.append(name.toUpper());
0047     //qCDebug(KMIME_LOG) << "KNMimeBase::cachedCharset() number of cs" << c_harsetCache.count();
0048     return c_harsetCache.last();
0049 }
0050 
0051 bool isUsAscii(const QString &s)
0052 {
0053     const uint sLength = s.length();
0054     for (uint i = 0; i < sLength; i++) {
0055         if (s.at(i).toLatin1() <= 0) {     // c==0: non-latin1, c<0: non-us-ascii
0056             return false;
0057         }
0058     }
0059     return true;
0060 }
0061 
0062 QString nameForEncoding(Headers::contentEncoding enc)
0063 {
0064     switch (enc) {
0065     case Headers::CE7Bit: return QStringLiteral("7bit");
0066     case Headers::CE8Bit: return QStringLiteral("8bit");
0067     case Headers::CEquPr: return QStringLiteral("quoted-printable");
0068     case Headers::CEbase64: return QStringLiteral("base64");
0069     case Headers::CEuuenc: return QStringLiteral("uuencode");
0070     case Headers::CEbinary: return QStringLiteral("binary");
0071     default: return QStringLiteral("unknown");
0072     }
0073 }
0074 
0075 QVector<Headers::contentEncoding> encodingsForData(const QByteArray &data)
0076 {
0077     QVector<Headers::contentEncoding> allowed;
0078     CharFreq cf(data);
0079 
0080     switch (cf.type()) {
0081     case CharFreq::SevenBitText:
0082         allowed << Headers::CE7Bit;
0083         Q_FALLTHROUGH();
0084     case CharFreq::EightBitText:
0085         allowed << Headers::CE8Bit;
0086         Q_FALLTHROUGH();
0087     case CharFreq::SevenBitData:
0088         if (cf.printableRatio() > 5.0 / 6.0) {
0089             // let n the length of data and p the number of printable chars.
0090             // Then base64 \approx 4n/3; qp \approx p + 3(n-p)
0091             // => qp < base64 iff p > 5n/6.
0092             allowed << Headers::CEquPr;
0093             allowed << Headers::CEbase64;
0094         } else {
0095             allowed << Headers::CEbase64;
0096             allowed << Headers::CEquPr;
0097         }
0098         break;
0099     case CharFreq::EightBitData:
0100         allowed << Headers::CEbase64;
0101         break;
0102     case CharFreq::None:
0103     default:
0104         Q_ASSERT(false);
0105     }
0106 
0107     return allowed;
0108 }
0109 
0110 // all except specials, CTLs, SPACE.
0111 const uchar aTextMap[16] = {
0112     0x00, 0x00, 0x00, 0x00,
0113     0x5F, 0x35, 0xFF, 0xC5,
0114     0x7F, 0xFF, 0xFF, 0xE3,
0115     0xFF, 0xFF, 0xFF, 0xFE
0116 };
0117 
0118 // all except tspecials, CTLs, SPACE.
0119 const uchar tTextMap[16] = {
0120     0x00, 0x00, 0x00, 0x00,
0121     0x5F, 0x36, 0xFF, 0xC0,
0122     0x7F, 0xFF, 0xFF, 0xE3,
0123     0xFF, 0xFF, 0xFF, 0xFE
0124 };
0125 
0126 void setUseOutlookAttachmentEncoding(bool violateStandard)
0127 {
0128     u_seOutlookEncoding = violateStandard;
0129 }
0130 
0131 bool useOutlookAttachmentEncoding()
0132 {
0133     return u_seOutlookEncoding;
0134 }
0135 
0136 QByteArray uniqueString()
0137 {
0138     static const char chars[] = "0123456789abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
0139     time_t now;
0140     char p[11];
0141     int ran;
0142     unsigned int timeval;
0143 
0144     p[10] = '\0';
0145     now = time(nullptr);
0146     ran = 1 + (int)(1000.0 * rand() / (RAND_MAX + 1.0));
0147     timeval = (now / ran) + QCoreApplication::applicationPid();
0148 
0149     for (int i = 0; i < 10; i++) {
0150         int pos = (int)(61.0 * rand() / (RAND_MAX + 1.0));
0151         //qCDebug(KMIME_LOG) << pos;
0152         p[i] = chars[pos];
0153     }
0154 
0155     QByteArray ret;
0156     ret.setNum(timeval);
0157     ret += '.';
0158     ret += p;
0159 
0160     return ret;
0161 }
0162 
0163 QByteArray multiPartBoundary()
0164 {
0165     return "nextPart" + uniqueString();
0166 }
0167 
0168 QByteArray unfoldHeader(const char *header, size_t headerSize)
0169 {
0170     QByteArray result;
0171     if (headerSize == 0) {
0172         return result;
0173     }
0174 
0175     // unfolding skips characters so result will be at worst headerSize long
0176     result.reserve(headerSize);
0177 
0178     const char *end = header + headerSize;
0179     const char *pos = header;
0180     const char *foldBegin = nullptr;
0181     const char *foldMid = nullptr;
0182     const char *foldEnd = nullptr;
0183     while ((foldMid = strchr(pos, '\n')) && foldMid < end) {
0184         foldBegin = foldEnd = foldMid;
0185         // find the first space before the line-break
0186         while (foldBegin > header) {
0187             if (!QChar::isSpace(*(foldBegin - 1))) {
0188                 break;
0189             }
0190             --foldBegin;
0191         }
0192         // find the first non-space after the line-break
0193         while (foldEnd <= end - 1) {
0194             if (QChar::isSpace(*foldEnd)) {
0195                 ++foldEnd;
0196             } else if (foldEnd && *(foldEnd - 1) == '\n' &&
0197                        *foldEnd == '=' && foldEnd + 2 < (header + headerSize - 1) &&
0198                        ((*(foldEnd + 1) == '0' &&
0199                          *(foldEnd + 2) == '9') ||
0200                         (*(foldEnd + 1) == '2' &&
0201                          *(foldEnd + 2) == '0'))) {
0202                 // bug #86302: malformed header continuation starting with =09/=20
0203                 foldEnd += 3;
0204             } else {
0205                 break;
0206             }
0207         }
0208 
0209         result.append(pos, foldBegin - pos);
0210         if (foldBegin != pos && foldEnd < end - 1) {
0211             result += ' ';
0212         }
0213         pos = foldEnd;
0214     }
0215     if (end > pos) {
0216         result.append(pos, end - pos);
0217     }
0218     return result;
0219 }
0220 
0221 QByteArray unfoldHeader(const QByteArray &header)
0222 {
0223     return unfoldHeader(header.constData(), header.size());
0224 }
0225 
0226 // state machine used by foldHeader()
0227 struct HeaderContext {
0228     unsigned int isEscapePair : 1;
0229     unsigned int isQuotedStr : 1;
0230 
0231     HeaderContext() {
0232         isEscapePair = isQuotedStr = 0;
0233     }
0234 
0235     void push(char c) {
0236         if (c == '\"' && !isEscapePair) {
0237             ++isQuotedStr;
0238         } else if (c == '\\' || isEscapePair) {
0239             ++isEscapePair;
0240         }
0241     }
0242 };
0243 
0244 QByteArray foldHeader(const QByteArray &header)
0245 {
0246     // RFC 5322 section 2.1.1. "Line Length Limits" says:
0247     //
0248     // "Each line of characters MUST be no more than 998 characters, and
0249     //  SHOULD be no more than 78 characters, excluding the CRLF."
0250     const int maxLen = 78;
0251 
0252     if (header.length() <= maxLen) {
0253         return header;
0254     }
0255 
0256     // fast forward to header body
0257     int pos = header.indexOf(':') + 1;
0258     if (pos <= 0 || pos >= header.length()) {
0259         return header;
0260     }
0261 
0262     // prepare for mutating header
0263     QByteArray hdr = header;
0264 
0265     // There are positions that are eligible for inserting FWS but discouraged
0266     // (e.g. existing white space within a quoted string), and there are
0267     // positions which are recommended for inserting FWS (e.g. after comma
0268     // separator of an address list).
0269     int eligible = pos;
0270     int recommended = pos;
0271 
0272     // reflects start position of "current line" in byte array
0273     int start = 0;
0274 
0275     HeaderContext ctx;
0276 
0277     for (; true; ++pos) {
0278         if (pos - start > maxLen && eligible) {
0279             // Fold line preferably at recommended position, at eligible position
0280             // otherwise.
0281             const int fws = recommended ? recommended : eligible;
0282             hdr.insert(fws, '\n');
0283             // We started a new line, so reset.
0284             if (eligible <= fws) {
0285                 eligible = 0;
0286             } else {
0287                 ++eligible; // LF
0288             }
0289             recommended = 0;
0290             start = fws + 1/* LF */;
0291             continue;
0292         }
0293 
0294         if (pos >= hdr.length()) {
0295             break;
0296         }
0297 
0298         // account for already inserted FWS
0299         // (NOTE: we are not caring about broken ones here)
0300         if (hdr[pos] == '\n') {
0301             recommended = eligible = 0;
0302             start = pos + 1/* LF */;
0303         }
0304 
0305         // Any white space character position is eligible for folding, except of
0306         // escape pair (i.e. BSP WSP must not be folded).
0307         if (hdr[pos] == ' ' && !ctx.isEscapePair && hdr[pos - 1] != '\n') {
0308             eligible = pos;
0309             if ((hdr[pos - 1] == ',' || hdr[pos - 1] == ';') && !ctx.isQuotedStr) {
0310                 recommended = pos;
0311             }
0312         }
0313 
0314         ctx.push(hdr[pos]);
0315     }
0316 
0317     return hdr;
0318 }
0319 
0320 int findHeaderLineEnd(const QByteArray &src, int &dataBegin, bool *folded)
0321 {
0322     int end = dataBegin;
0323     int len = src.length() - 1;
0324 
0325     if (folded) {
0326         *folded = false;
0327     }
0328 
0329     if (dataBegin < 0) {
0330         // Not found
0331         return -1;
0332     }
0333 
0334     if (dataBegin > len) {
0335         // No data available
0336         return len + 1;
0337     }
0338 
0339     // If the first line contains nothing, but the next line starts with a space
0340     // or a tab, that means a stupid mail client has made the first header field line
0341     // entirely empty, and has folded the rest to the next line(s).
0342     if (src.at(end) == '\n' && end + 1 < len &&
0343             (src[end + 1] == ' ' || src[end + 1] == '\t')) {
0344 
0345         // Skip \n and first whitespace
0346         dataBegin += 2;
0347         end += 2;
0348     }
0349 
0350     if (src.at(end) != '\n') {      // check if the header is not empty
0351         while (true) {
0352             end = src.indexOf('\n', end + 1);
0353             if (end == -1 || end == len) {
0354                 // end of string
0355                 break;
0356             } else if (src[end + 1] == ' ' || src[end + 1] == '\t' ||
0357                        (src[end + 1] == '=' && end + 3 <= len &&
0358                         ((src[end + 2] == '0' && src[end + 3] == '9') ||
0359                          (src[end + 2] == '2' && src[end + 3] == '0')))) {
0360                 // next line is header continuation or starts with =09/=20 (bug #86302)
0361                 if (folded) {
0362                     *folded = true;
0363                 }
0364             } else {
0365                 // end of header (no header continuation)
0366                 break;
0367             }
0368         }
0369     }
0370 
0371     if (end < 0) {
0372         end = len + 1; //take the rest of the string
0373     }
0374     return end;
0375 }
0376 
0377 #if !HAVE_STRCASESTR
0378 #ifdef WIN32
0379 #define strncasecmp _strnicmp
0380 #endif
0381 static const char *strcasestr(const char *haystack, const char *needle)
0382 {
0383     /* Copied from libreplace as part of qtwebengine 5.5.1 */
0384     const char *s;
0385     size_t nlen = strlen(needle);
0386     for (s = haystack; *s; s++) {
0387         if (toupper(*needle) == toupper(*s) && strncasecmp(s, needle, nlen) == 0) {
0388             return (char *)((uintptr_t)s);
0389         }
0390     }
0391     return NULL;
0392 }
0393 #endif
0394 
0395 int indexOfHeader(const QByteArray &src, const QByteArray &name, int &end, int &dataBegin, bool *folded)
0396 {
0397     QByteArray n = name;
0398     n.append(':');
0399     int begin = -1;
0400 
0401     if (qstrnicmp(n.constData(), src.constData(), n.length()) == 0) {
0402         begin = 0;
0403     } else {
0404         n.prepend('\n');
0405         const char *p = strcasestr(src.constData(), n.constData());
0406         if (!p) {
0407             begin = -1;
0408         } else {
0409             begin = p - src.constData();
0410             ++begin;
0411         }
0412     }
0413 
0414     if (begin > -1) {       //there is a header with the given name
0415         dataBegin = begin + name.length() + 1; //skip the name
0416         // skip the usual space after the colon
0417         if (dataBegin < src.length() && src.at(dataBegin) == ' ') {
0418             ++dataBegin;
0419         }
0420         end = findHeaderLineEnd(src, dataBegin, folded);
0421         return begin;
0422 
0423     } else {
0424         end = -1;
0425         dataBegin = -1;
0426         return -1; //header not found
0427     }
0428 }
0429 
0430 QByteArray extractHeader(const QByteArray &src, const QByteArray &name)
0431 {
0432     int begin;
0433     int end;
0434     bool folded;
0435     QByteArray result;
0436 
0437     if (src.isEmpty() || indexOfHeader(src, name, end, begin, &folded) < 0) {
0438         return result;
0439     }
0440 
0441     if (begin >= 0) {
0442         if (!folded) {
0443             result = src.mid(begin, end - begin);
0444         } else {
0445             if (end > begin) {
0446                 result = unfoldHeader(src.constData() + begin, end - begin);
0447             }
0448         }
0449     }
0450     return result;
0451 }
0452 
0453 QByteArray CRLFtoLF(const QByteArray &s)
0454 {
0455     if (!s.contains("\r\n")) {
0456         return s;
0457     }
0458 
0459     QByteArray ret = s;
0460     ret.replace("\r\n", "\n");
0461     return ret;
0462 }
0463 
0464 QByteArray CRLFtoLF(const char *s)
0465 {
0466     QByteArray ret = s;
0467     return CRLFtoLF(ret);
0468 }
0469 
0470 QByteArray LFtoCRLF(const QByteArray &s)
0471 {
0472     const int firstNewline = s.indexOf('\n');
0473     if (firstNewline == -1) {
0474         return s;
0475     }
0476     if (firstNewline > 0 && s.at(firstNewline - 1) == '\r') {
0477         // We found \r\n already, don't change anything
0478         // This check assumes that input is consistent in terms of newlines,
0479         // but so did if (s.contains("\r\n")), too.
0480         return s;
0481     }
0482 
0483     QByteArray ret = s;
0484     ret.replace('\n', "\r\n");
0485     return ret;
0486 }
0487 
0488 QByteArray LFtoCRLF(const char *s)
0489 {
0490     QByteArray ret = s;
0491     return LFtoCRLF(ret);
0492 }
0493 
0494 QByteArray CRtoLF(const QByteArray &s)
0495 {
0496     const int firstNewline = s.indexOf('\r');
0497     if (firstNewline == -1) {
0498         return s;
0499     }
0500     if (firstNewline > 0 && (s.length() > firstNewline + 1) && s.at(firstNewline + 1) == '\n') {
0501         // We found \r\n already, don't change anything
0502         // This check assumes that input is consistent in terms of newlines,
0503         // but so did if (s.contains("\r\n")), too.
0504         return s;
0505     }
0506 
0507     QByteArray ret = s;
0508     ret.replace('\r', '\n');
0509     return ret;
0510 }
0511 
0512 QByteArray CRtoLF(const char *s)
0513 {
0514     const QByteArray ret = s;
0515     return CRtoLF(ret);
0516 }
0517 
0518 namespace
0519 {
0520 template < typename StringType, typename CharType > void removeQuotesGeneric(StringType &str)
0521 {
0522     bool inQuote = false;
0523     for (int i = 0; i < str.length(); ++i) {
0524         if (str[i] == CharType('"')) {
0525             str.remove(i, 1);
0526             i--;
0527             inQuote = !inQuote;
0528         } else {
0529             if (inQuote && (str[i] == CharType('\\'))) {
0530                 str.remove(i, 1);
0531             }
0532         }
0533     }
0534 }
0535 }
0536 
0537 void removeQuotes(QByteArray &str)
0538 {
0539     removeQuotesGeneric<QByteArray, char>(str);
0540 }
0541 
0542 void removeQuotes(QString &str)
0543 {
0544     removeQuotesGeneric<QString, QLatin1Char>(str);
0545 }
0546 
0547 template<class StringType, class CharType, class CharConverterType, class StringConverterType, class ToString>
0548 void addQuotes_impl(StringType &str, bool forceQuotes)
0549 {
0550     bool needsQuotes = false;
0551     for (int i = 0; i < str.length(); i++) {
0552         const CharType cur = str.at(i);
0553         if (QString(ToString(str)).contains(QRegularExpression(QStringLiteral("\"|\\\\|=|\\]|\\[|:|;|,|\\.|,|@|<|>|\\)|\\(")))) {
0554             needsQuotes = true;
0555         }
0556         if (cur == CharConverterType('\\') || cur == CharConverterType('\"')) {
0557             str.insert(i, CharConverterType('\\'));
0558             i++;
0559         }
0560     }
0561 
0562     if (needsQuotes || forceQuotes) {
0563         str.insert(0, CharConverterType('\"'));
0564         str.append(StringConverterType("\""));
0565     }
0566 }
0567 
0568 void addQuotes(QByteArray &str, bool forceQuotes)
0569 {
0570     addQuotes_impl<QByteArray, char, char, char *, QLatin1String>(str, forceQuotes);
0571 }
0572 
0573 void addQuotes(QString &str, bool forceQuotes)
0574 {
0575     addQuotes_impl<QString, QChar, QLatin1Char, QLatin1String, QString>(str, forceQuotes);
0576 }
0577 
0578 KMIME_EXPORT QString balanceBidiState(const QString &input)
0579 {
0580     const int LRO = 0x202D;
0581     const int RLO = 0x202E;
0582     const int LRE = 0x202A;
0583     const int RLE = 0x202B;
0584     const int PDF = 0x202C;
0585 
0586     QString result = input;
0587 
0588     int openDirChangers = 0;
0589     int numPDFsRemoved = 0;
0590     for (int i = 0; i < input.length(); i++) {
0591         const ushort &code = input.at(i).unicode();
0592         if (code == LRO || code == RLO || code == LRE || code == RLE) {
0593             openDirChangers++;
0594         } else if (code == PDF) {
0595             if (openDirChangers > 0) {
0596                 openDirChangers--;
0597             } else {
0598                 // One PDF too much, remove it
0599                 qCWarning(KMIME_LOG) << "Possible Unicode spoofing (unexpected PDF) detected in" << input;
0600                 result.remove(i - numPDFsRemoved, 1);
0601                 numPDFsRemoved++;
0602             }
0603         }
0604     }
0605 
0606     if (openDirChangers > 0) {
0607         qCWarning(KMIME_LOG) << "Possible Unicode spoofing detected in" << input;
0608 
0609         // At PDF chars to the end until the correct state is restored.
0610         // As a special exception, when encountering quoted strings, place the PDF before
0611         // the last quote.
0612         for (int i = openDirChangers; i > 0; i--) {
0613             if (result.endsWith(QLatin1Char('"'))) {
0614                 result.insert(result.length() - 1, QChar(PDF));
0615             } else {
0616                 result += QChar(PDF);
0617             }
0618         }
0619     }
0620 
0621     return result;
0622 }
0623 
0624 QString removeBidiControlChars(const QString &input)
0625 {
0626     const int LRO = 0x202D;
0627     const int RLO = 0x202E;
0628     const int LRE = 0x202A;
0629     const int RLE = 0x202B;
0630     QString result = input;
0631     result.remove(QChar(LRO));
0632     result.remove(QChar(RLO));
0633     result.remove(QChar(LRE));
0634     result.remove(QChar(RLE));
0635     return result;
0636 }
0637 
0638 bool isCryptoPart(Content *content)
0639 {
0640     auto ct = content->contentType(false);
0641     if (!ct || !ct->isMediatype("application")) {
0642         return false;
0643     }
0644 
0645     const QByteArray lowerSubType = ct->subType().toLower();
0646     if (lowerSubType == "pgp-encrypted" ||
0647         lowerSubType == "pgp-signature" ||
0648         lowerSubType == "pkcs7-mime" ||
0649         lowerSubType == "x-pkcs7-mime" ||
0650         lowerSubType == "pkcs7-signature" ||
0651         lowerSubType == "x-pkcs7-signature") {
0652         return true;
0653     }
0654 
0655     if (lowerSubType == "octet-stream") {
0656         auto cd = content->contentDisposition(false);
0657         if (!cd) {
0658             return false;
0659         }
0660         const auto fileName = cd->filename().toLower();
0661         return fileName == QLatin1String("msg.asc") || fileName == QLatin1String("encrypted.asc");
0662     }
0663 
0664     return false;
0665 }
0666 
0667 bool isAttachment(Content* content)
0668 {
0669     if (!content) {
0670         return false;
0671     }
0672 
0673     const auto contentType = content->contentType(false);
0674     // multipart/* is never an attachment itself, message/rfc822 always is
0675     if (contentType) {
0676         if (contentType->isMultipart()) {
0677             return false;
0678         }
0679         if (contentType->isMimeType("message/rfc822")) {
0680             return true;
0681         }
0682     }
0683 
0684     // the main body part is not an attachment
0685     if (content->parent()) {
0686         const auto top = content->topLevel();
0687         if (content == top->textContent()) {
0688             return false;
0689         }
0690     }
0691 
0692     // ignore crypto parts
0693     if (isCryptoPart(content)) {
0694         return false;
0695     }
0696 
0697     // content type or content disposition having a file name set looks like an attachment
0698     const auto contentDisposition = content->contentDisposition(false);
0699     if (contentDisposition && !contentDisposition->filename().isEmpty()) {
0700         return true;
0701     }
0702 
0703     if (contentType && !contentType->name().isEmpty()) {
0704         return true;
0705     }
0706 
0707     // "attachment" content disposition is otherwise a good indicator though
0708     if (contentDisposition && contentDisposition->disposition() == Headers::CDattachment) {
0709         return true;
0710     }
0711 
0712     return false;
0713 }
0714 
0715 bool hasAttachment(Content *content)
0716 {
0717     if (!content) {
0718         return false;
0719     }
0720 
0721     if (isAttachment(content)) {
0722         return true;
0723     }
0724 
0725     // Ok, content itself is not an attachment. now we deal with multiparts
0726     auto ct = content->contentType(false);
0727     if (ct && ct->isMultipart() && !ct->isSubtype("related")) {// && !ct->isSubtype("alternative")) {
0728         const auto contents = content->contents();
0729         for (Content *child : contents) {
0730             if (hasAttachment(child)) {
0731                 return true;
0732             }
0733         }
0734     }
0735     return false;
0736 }
0737 
0738 bool hasInvitation(Content *content)
0739 {
0740     if (!content) {
0741         return false;
0742     }
0743 
0744     if (isInvitation(content)) {
0745         return true;
0746     }
0747 
0748     // Ok, content itself is not an invitation. now we deal with multiparts
0749     if (content->contentType()->isMultipart()) {
0750         const auto contents = content->contents();
0751         for (Content *child : contents) {
0752             if (hasInvitation(child)) {
0753                 return true;
0754             }
0755         }
0756     }
0757     return false;
0758 }
0759 
0760 bool isSigned(Message *message)
0761 {
0762     if (!message) {
0763         return false;
0764     }
0765 
0766     const KMime::Headers::ContentType *const contentType = message->contentType();
0767     if (contentType->isSubtype("signed") ||
0768             contentType->isSubtype("pgp-signature") ||
0769             contentType->isSubtype("pkcs7-signature") ||
0770             contentType->isSubtype("x-pkcs7-signature") ||
0771             message->mainBodyPart("multipart/signed") ||
0772             message->mainBodyPart("application/pgp-signature") ||
0773             message->mainBodyPart("application/pkcs7-signature") ||
0774             message->mainBodyPart("application/x-pkcs7-signature")) {
0775         return true;
0776     }
0777     return false;
0778 }
0779 
0780 bool isEncrypted(Message *message)
0781 {
0782     if (!message) {
0783         return false;
0784     }
0785 
0786     const KMime::Headers::ContentType *const contentType = message->contentType();
0787     if (contentType->isSubtype("encrypted") ||
0788             contentType->isSubtype("pgp-encrypted") ||
0789             contentType->isSubtype("pkcs7-mime") ||
0790             contentType->isSubtype("x-pkcs7-mime") ||
0791             message->mainBodyPart("multipart/encrypted") ||
0792             message->mainBodyPart("application/pgp-encrypted") ||
0793             message->mainBodyPart("application/pkcs7-mime") ||
0794             message->mainBodyPart("application/x-pkcs7-mime")) {
0795         return true;
0796     }
0797 
0798     return false;
0799 }
0800 
0801 bool isInvitation(Content *content)
0802 {
0803     if (!content) {
0804         return false;
0805     }
0806 
0807     const KMime::Headers::ContentType *const contentType = content->contentType(false);
0808 
0809     if (contentType && contentType->isMediatype("text") && contentType->isSubtype("calendar")) {
0810         return true;
0811     }
0812 
0813     return false;
0814 }
0815 
0816 } // namespace KMime