File indexing completed on 2024-04-28 09:12:11

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_p.h"
0015 #include "kmime_debug.h"
0016 #include "kmime_header_parsing.h"
0017 #include "kmime_message.h"
0018 #include "kmime_warning_p.h"
0019 
0020 #include <QCoreApplication>
0021 
0022 #include <algorithm>
0023 #include <cctype>
0024 #include <cstdlib>
0025 #include <ctime>
0026 
0027 using namespace KMime;
0028 
0029 namespace KMime
0030 {
0031 
0032 QList<QByteArray> c_harsetCache;
0033 
0034 QByteArray cachedCharset(const QByteArray &name)
0035 {
0036     for (const QByteArray &charset : std::as_const(c_harsetCache)) {
0037         if (qstricmp(name.data(), charset.data()) == 0) {
0038             return charset;
0039         }
0040     }
0041 
0042     c_harsetCache.append(name.toUpper());
0043     //qCDebug(KMIME_LOG) << "KNMimeBase::cachedCharset() number of cs" << c_harsetCache.count();
0044     return c_harsetCache.last();
0045 }
0046 
0047 bool isUsAscii(const QString &s)
0048 {
0049     const uint sLength = s.length();
0050     for (uint i = 0; i < sLength; i++) {
0051         if (s.at(i).toLatin1() <= 0) {     // c==0: non-latin1, c<0: non-us-ascii
0052             return false;
0053         }
0054     }
0055     return true;
0056 }
0057 
0058 QString nameForEncoding(Headers::contentEncoding enc)
0059 {
0060     switch (enc) {
0061     case Headers::CE7Bit: return QStringLiteral("7bit");
0062     case Headers::CE8Bit: return QStringLiteral("8bit");
0063     case Headers::CEquPr: return QStringLiteral("quoted-printable");
0064     case Headers::CEbase64: return QStringLiteral("base64");
0065     case Headers::CEuuenc: return QStringLiteral("uuencode");
0066     case Headers::CEbinary: return QStringLiteral("binary");
0067     default: return QStringLiteral("unknown");
0068     }
0069 }
0070 
0071 QList<Headers::contentEncoding> encodingsForData(const QByteArray &data) {
0072     QList<Headers::contentEncoding> allowed;
0073     CharFreq cf(data);
0074 
0075     switch (cf.type()) {
0076     case CharFreq::SevenBitText:
0077         allowed << Headers::CE7Bit;
0078         [[fallthrough]];
0079     case CharFreq::EightBitText:
0080         allowed << Headers::CE8Bit;
0081         [[fallthrough]];
0082     case CharFreq::SevenBitData:
0083         if (cf.printableRatio() > 5.0 / 6.0) {
0084             // let n the length of data and p the number of printable chars.
0085             // Then base64 \approx 4n/3; qp \approx p + 3(n-p)
0086             // => qp < base64 iff p > 5n/6.
0087             allowed << Headers::CEquPr;
0088             allowed << Headers::CEbase64;
0089         } else {
0090             allowed << Headers::CEbase64;
0091             allowed << Headers::CEquPr;
0092         }
0093         break;
0094     case CharFreq::EightBitData:
0095         allowed << Headers::CEbase64;
0096         break;
0097     case CharFreq::None:
0098     default:
0099         Q_ASSERT(false);
0100     }
0101 
0102     return allowed;
0103 }
0104 
0105 // all except specials, CTLs, SPACE.
0106 const uchar aTextMap[16] = {
0107     0x00, 0x00, 0x00, 0x00,
0108     0x5F, 0x35, 0xFF, 0xC5,
0109     0x7F, 0xFF, 0xFF, 0xE3,
0110     0xFF, 0xFF, 0xFF, 0xFE
0111 };
0112 
0113 // all except tspecials, CTLs, SPACE.
0114 const uchar tTextMap[16] = {
0115     0x00, 0x00, 0x00, 0x00,
0116     0x5F, 0x36, 0xFF, 0xC0,
0117     0x7F, 0xFF, 0xFF, 0xE3,
0118     0xFF, 0xFF, 0xFF, 0xFE
0119 };
0120 
0121 QByteArray uniqueString()
0122 {
0123     static const char chars[] = "0123456789abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
0124     time_t now;
0125     char p[11];
0126     int ran;
0127     unsigned int timeval;
0128 
0129     p[10] = '\0';
0130     now = time(nullptr);
0131     ran = 1 + (int)(1000.0 * rand() / (RAND_MAX + 1.0));
0132     timeval = (now / ran) + QCoreApplication::applicationPid();
0133 
0134     for (int i = 0; i < 10; i++) {
0135         int pos = (int)(61.0 * rand() / (RAND_MAX + 1.0));
0136         //qCDebug(KMIME_LOG) << pos;
0137         p[i] = chars[pos];
0138     }
0139 
0140     QByteArray ret;
0141     ret.setNum(timeval);
0142     ret += '.';
0143     ret += p;
0144 
0145     return ret;
0146 }
0147 
0148 QByteArray multiPartBoundary()
0149 {
0150     return "nextPart" + uniqueString();
0151 }
0152 
0153 QByteArray CRLFtoLF(const QByteArray &s)
0154 {
0155     if (!s.contains("\r\n")) {
0156         return s;
0157     }
0158 
0159     QByteArray ret = s;
0160     ret.replace("\r\n", "\n");
0161     return ret;
0162 }
0163 
0164 QByteArray CRLFtoLF(const char *s)
0165 {
0166     QByteArray ret = s;
0167     return CRLFtoLF(ret);
0168 }
0169 
0170 QByteArray LFtoCRLF(const QByteArray &s)
0171 {
0172     const int firstNewline = s.indexOf('\n');
0173     if (firstNewline == -1) {
0174         return s;
0175     }
0176     if (firstNewline > 0 && s.at(firstNewline - 1) == '\r') {
0177         // We found \r\n already, don't change anything
0178         // This check assumes that input is consistent in terms of newlines,
0179         // but so did if (s.contains("\r\n")), too.
0180         return s;
0181     }
0182 
0183     QByteArray ret = s;
0184     ret.replace('\n', "\r\n");
0185     return ret;
0186 }
0187 
0188 QByteArray LFtoCRLF(const char *s)
0189 {
0190     QByteArray ret = s;
0191     return LFtoCRLF(ret);
0192 }
0193 
0194 bool isCryptoPart(Content *content)
0195 {
0196     auto ct = content->contentType(false);
0197     if (!ct || !ct->isMediatype("application")) {
0198         return false;
0199     }
0200 
0201     const QByteArray lowerSubType = ct->subType().toLower();
0202     if (lowerSubType == "pgp-encrypted" ||
0203         lowerSubType == "pgp-signature" ||
0204         lowerSubType == "pkcs7-mime" ||
0205         lowerSubType == "x-pkcs7-mime" ||
0206         lowerSubType == "pkcs7-signature" ||
0207         lowerSubType == "x-pkcs7-signature") {
0208         return true;
0209     }
0210 
0211     if (lowerSubType == "octet-stream") {
0212         auto cd = content->contentDisposition(false);
0213         if (!cd) {
0214             return false;
0215         }
0216         const auto fileName = cd->filename().toLower();
0217         return fileName == QLatin1String("msg.asc") || fileName == QLatin1String("encrypted.asc");
0218     }
0219 
0220     return false;
0221 }
0222 
0223 bool isAttachment(Content* content)
0224 {
0225     if (!content) {
0226         return false;
0227     }
0228 
0229     const auto contentType = content->contentType(false);
0230     // multipart/* is never an attachment itself, message/rfc822 always is
0231     if (contentType) {
0232         if (contentType->isMultipart()) {
0233             return false;
0234         }
0235         if (contentType->isMimeType("message/rfc822")) {
0236             return true;
0237         }
0238     }
0239 
0240     // the main body part is not an attachment
0241     if (content->parent()) {
0242         const auto top = content->topLevel();
0243         if (content == top->textContent()) {
0244             return false;
0245         }
0246     }
0247 
0248     // ignore crypto parts
0249     if (isCryptoPart(content)) {
0250         return false;
0251     }
0252 
0253     // content type or content disposition having a file name set looks like an attachment
0254     const auto contentDisposition = content->contentDisposition(false);
0255     if (contentDisposition && !contentDisposition->filename().isEmpty()) {
0256         return true;
0257     }
0258 
0259     if (contentType && !contentType->name().isEmpty()) {
0260         return true;
0261     }
0262 
0263     // "attachment" content disposition is otherwise a good indicator though
0264     if (contentDisposition && contentDisposition->disposition() == Headers::CDattachment) {
0265         return true;
0266     }
0267 
0268     return false;
0269 }
0270 
0271 bool hasAttachment(Content *content)
0272 {
0273     if (!content) {
0274         return false;
0275     }
0276 
0277     if (isAttachment(content)) {
0278         return true;
0279     }
0280 
0281     // Ok, content itself is not an attachment. now we deal with multiparts
0282     auto ct = content->contentType(false);
0283     if (ct && ct->isMultipart() && !ct->isSubtype("related")) {// && !ct->isSubtype("alternative")) {
0284         const auto contents = content->contents();
0285         for (Content *child : contents) {
0286             if (hasAttachment(child)) {
0287                 return true;
0288             }
0289         }
0290     }
0291     return false;
0292 }
0293 
0294 bool hasInvitation(Content *content)
0295 {
0296     if (!content) {
0297         return false;
0298     }
0299 
0300     if (isInvitation(content)) {
0301         return true;
0302     }
0303 
0304     // Ok, content itself is not an invitation. now we deal with multiparts
0305     if (content->contentType()->isMultipart()) {
0306         const auto contents = content->contents();
0307         for (Content *child : contents) {
0308             if (hasInvitation(child)) {
0309                 return true;
0310             }
0311         }
0312     }
0313     return false;
0314 }
0315 
0316 bool isSigned(Message *message)
0317 {
0318     if (!message) {
0319         return false;
0320     }
0321 
0322     const KMime::Headers::ContentType *const contentType = message->contentType();
0323     if (contentType->isSubtype("signed") ||
0324             contentType->isSubtype("pgp-signature") ||
0325             contentType->isSubtype("pkcs7-signature") ||
0326             contentType->isSubtype("x-pkcs7-signature") ||
0327             message->mainBodyPart("multipart/signed") ||
0328             message->mainBodyPart("application/pgp-signature") ||
0329             message->mainBodyPart("application/pkcs7-signature") ||
0330             message->mainBodyPart("application/x-pkcs7-signature")) {
0331         return true;
0332     }
0333     return false;
0334 }
0335 
0336 bool isEncrypted(Message *message)
0337 {
0338     if (!message) {
0339         return false;
0340     }
0341 
0342     const KMime::Headers::ContentType *const contentType = message->contentType();
0343     if (contentType->isSubtype("encrypted") ||
0344             contentType->isSubtype("pgp-encrypted") ||
0345             contentType->isSubtype("pkcs7-mime") ||
0346             contentType->isSubtype("x-pkcs7-mime") ||
0347             message->mainBodyPart("multipart/encrypted") ||
0348             message->mainBodyPart("application/pgp-encrypted") ||
0349             message->mainBodyPart("application/pkcs7-mime") ||
0350             message->mainBodyPart("application/x-pkcs7-mime")) {
0351         return true;
0352     }
0353 
0354     return false;
0355 }
0356 
0357 bool isInvitation(Content *content)
0358 {
0359     if (!content) {
0360         return false;
0361     }
0362 
0363     const KMime::Headers::ContentType *const contentType = content->contentType(false);
0364 
0365     if (contentType && contentType->isMediatype("text") && contentType->isSubtype("calendar")) {
0366         return true;
0367     }
0368 
0369     return false;
0370 }
0371 
0372 } // namespace KMime