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