File indexing completed on 2023-09-24 09:25:04

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