File indexing completed on 2024-05-12 05:11:09

0001 /*
0002     SPDX-FileCopyrightText: 2007 Till Adam <adam@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "akonadi_serializer_mail.h"
0008 #include "akonadi_serializer_mail_debug.h"
0009 #include "messageparts.h"
0010 
0011 #include <QDataStream>
0012 #include <QIODevice>
0013 #include <qplugin.h>
0014 
0015 #include <KMime/Message>
0016 
0017 #include <Akonadi/Item>
0018 #include <akonadi/private/imapparser_p.h>
0019 
0020 using namespace Akonadi;
0021 using namespace KMime;
0022 
0023 QString StringPool::sharedValue(const QString &value)
0024 {
0025     QMutexLocker lock(&m_mutex);
0026     QSet<QString>::const_iterator it = m_pool.constFind(value);
0027     if (it != m_pool.constEnd()) {
0028         return *it;
0029     }
0030     m_pool.insert(value);
0031     return value;
0032 }
0033 
0034 template<typename T>
0035 static void parseAddrList(const QVarLengthArray<QByteArray, 16> &addrList, T *hdr, int version, StringPool &pool)
0036 {
0037     hdr->clear();
0038     const int count = addrList.count();
0039     QVarLengthArray<QByteArray, 16> addr;
0040     for (int i = 0; i < count; ++i) {
0041         ImapParser::parseParenthesizedList(addrList[i], addr);
0042         if (addr.count() != 4) {
0043             qCWarning(AKONADI_SERIALIZER_MAIL_LOG) << "Error parsing envelope address field: " << addrList[i];
0044             continue;
0045         }
0046         KMime::Types::Mailbox addrField;
0047         if (version == 0) {
0048             addrField.setNameFrom7Bit(addr[0]);
0049         } else if (version == 1) {
0050             addrField.setName(pool.sharedValue(QString::fromUtf8(addr[0])));
0051         }
0052         KMime::Types::AddrSpec addrSpec;
0053         addrSpec.localPart = pool.sharedValue(QString::fromUtf8(addr[2]));
0054         addrSpec.domain = pool.sharedValue(QString::fromUtf8(addr[3]));
0055         addrField.setAddress(addrSpec);
0056         hdr->addAddress(addrField);
0057     }
0058 }
0059 
0060 template<typename T>
0061 static void parseAddrList(QDataStream &stream, T *hdr, int version, StringPool &pool)
0062 {
0063     Q_UNUSED(version)
0064 
0065     hdr->clear();
0066     int count = 0;
0067     stream >> count;
0068     for (int i = 0; i < count; ++i) {
0069         QString str;
0070         KMime::Types::Mailbox mbox;
0071         KMime::Types::AddrSpec addrSpec;
0072         stream >> str;
0073         mbox.setName(pool.sharedValue(str));
0074         stream >> str;
0075         addrSpec.localPart = pool.sharedValue(str);
0076         stream >> str;
0077         addrSpec.domain = pool.sharedValue(str);
0078         mbox.setAddress(addrSpec);
0079 
0080         hdr->addAddress(mbox);
0081     }
0082 }
0083 
0084 bool SerializerPluginMail::deserialize(Item &item, const QByteArray &label, QIODevice &data, int version)
0085 {
0086     if (label != MessagePart::Body && label != MessagePart::Envelope && label != MessagePart::Header) {
0087         return false;
0088     }
0089 
0090     KMime::Message::Ptr msg;
0091     if (!item.hasPayload()) {
0092         auto m = new Message();
0093         msg = KMime::Message::Ptr(m);
0094         item.setPayload(msg);
0095     } else {
0096         msg = item.payload<KMime::Message::Ptr>();
0097     }
0098 
0099     if (label == MessagePart::Body) {
0100         QByteArray buffer = data.readAll();
0101         if (buffer.isEmpty()) {
0102             return true;
0103         }
0104         msg->setContent(buffer);
0105         msg->parse();
0106     } else if (label == MessagePart::Header) {
0107         QByteArray buffer = data.readAll();
0108         if (buffer.isEmpty()) {
0109             return true;
0110         }
0111         if (msg->body().isEmpty() && msg->contents().isEmpty()) {
0112             msg->setHead(buffer);
0113             msg->parse();
0114         }
0115     } else if (label == MessagePart::Envelope) {
0116         if (version <= 1) {
0117             QByteArray buffer = data.readAll();
0118             if (buffer.isEmpty()) {
0119                 return true;
0120             }
0121             QVarLengthArray<QByteArray, 16> env;
0122             ImapParser::parseParenthesizedList(buffer, env);
0123             if (env.count() < 10) {
0124                 qCWarning(AKONADI_SERIALIZER_MAIL_LOG) << "Akonadi KMime Deserializer: Got invalid envelope: " << buffer;
0125                 return false;
0126             }
0127             Q_ASSERT(env.count() >= 10);
0128             // date
0129             msg->date()->from7BitString(env[0]);
0130             // subject
0131             msg->subject()->from7BitString(env[1]);
0132             // from
0133             QVarLengthArray<QByteArray, 16> addrList;
0134             ImapParser::parseParenthesizedList(env[2], addrList);
0135             if (!addrList.isEmpty()) {
0136                 parseAddrList(addrList, msg->from(), version, m_stringPool);
0137             }
0138             // sender
0139             ImapParser::parseParenthesizedList(env[3], addrList);
0140             if (!addrList.isEmpty()) {
0141                 parseAddrList(addrList, msg->sender(), version, m_stringPool);
0142             }
0143             // reply-to
0144             ImapParser::parseParenthesizedList(env[4], addrList);
0145             if (!addrList.isEmpty()) {
0146                 parseAddrList(addrList, msg->replyTo(), version, m_stringPool);
0147             }
0148             // to
0149             ImapParser::parseParenthesizedList(env[5], addrList);
0150             if (!addrList.isEmpty()) {
0151                 parseAddrList(addrList, msg->to(), version, m_stringPool);
0152             }
0153             // cc
0154             ImapParser::parseParenthesizedList(env[6], addrList);
0155             if (!addrList.isEmpty()) {
0156                 parseAddrList(addrList, msg->cc(), version, m_stringPool);
0157             }
0158             // bcc
0159             ImapParser::parseParenthesizedList(env[7], addrList);
0160             if (!addrList.isEmpty()) {
0161                 parseAddrList(addrList, msg->bcc(), version, m_stringPool);
0162             }
0163             // in-reply-to
0164             msg->inReplyTo()->from7BitString(env[8]);
0165             // message id
0166             msg->messageID()->from7BitString(env[9]);
0167             // references
0168             if (env.count() > 10) {
0169                 msg->references()->from7BitString(env[10]);
0170             }
0171         } else if (version == 2) {
0172             QDataStream stream(&data);
0173             QDateTime dt;
0174             QString str;
0175 
0176             stream >> dt;
0177             msg->date()->setDateTime(dt);
0178             stream >> str;
0179             msg->subject()->fromUnicodeString(str, QByteArrayLiteral("UTF-8"));
0180 
0181             QString inReplyTo;
0182             stream >> inReplyTo;
0183             msg->inReplyTo()->fromUnicodeString(inReplyTo, QByteArrayLiteral("UTF-8"));
0184             stream >> str;
0185             msg->messageID()->fromUnicodeString(str, QByteArrayLiteral("UTF-8"));
0186             stream >> str;
0187             if (str == inReplyTo) {
0188                 msg->references()->fromIdent(msg->inReplyTo());
0189             } else {
0190                 msg->references()->fromUnicodeString(str, QByteArrayLiteral("UTF-8"));
0191             }
0192 
0193             parseAddrList(stream, msg->from(), version, m_stringPool);
0194             parseAddrList(stream, msg->sender(), version, m_stringPool);
0195             parseAddrList(stream, msg->replyTo(), version, m_stringPool);
0196             parseAddrList(stream, msg->to(), version, m_stringPool);
0197             parseAddrList(stream, msg->cc(), version, m_stringPool);
0198             parseAddrList(stream, msg->bcc(), version, m_stringPool);
0199 
0200             if (stream.status() == QDataStream::ReadCorruptData || stream.status() == QDataStream::ReadPastEnd) {
0201                 qCWarning(AKONADI_SERIALIZER_MAIL_LOG) << "Akonadi KMime Deserializer: Got invalid envelope";
0202                 return false;
0203             }
0204         }
0205     }
0206 
0207     return true;
0208 }
0209 
0210 template<typename T>
0211 static void serializeAddrList(QDataStream &stream, T *hdr)
0212 {
0213     const KMime::Types::Mailbox::List mb = hdr->mailboxes();
0214     stream << (qint32)mb.size();
0215     for (const KMime::Types::Mailbox &mbox : mb) {
0216         stream << mbox.name() << mbox.addrSpec().localPart << mbox.addrSpec().domain;
0217     }
0218 }
0219 
0220 void SerializerPluginMail::serialize(const Item &item, const QByteArray &label, QIODevice &data, int &version)
0221 {
0222     version = 1;
0223 
0224     auto m = item.payload<KMime::Message::Ptr>();
0225     if (label == MessagePart::Body) {
0226         data.write(m->encodedContent());
0227     } else if (label == MessagePart::Envelope) {
0228         version = 2;
0229         QDataStream stream(&data);
0230         stream << m->date()->dateTime() << m->subject()->asUnicodeString() << m->inReplyTo()->asUnicodeString() << m->messageID()->asUnicodeString()
0231                << m->references()->asUnicodeString();
0232         serializeAddrList(stream, m->from());
0233         serializeAddrList(stream, m->sender());
0234         serializeAddrList(stream, m->replyTo());
0235         serializeAddrList(stream, m->to());
0236         serializeAddrList(stream, m->cc());
0237         serializeAddrList(stream, m->bcc());
0238     } else if (label == MessagePart::Header) {
0239         data.write(m->head());
0240     }
0241 }
0242 
0243 QSet<QByteArray> SerializerPluginMail::parts(const Item &item) const
0244 {
0245     QSet<QByteArray> set;
0246 
0247     if (!item.hasPayload<KMime::Message::Ptr>()) {
0248         return set;
0249     }
0250 
0251     auto msg = item.payload<KMime::Message::Ptr>();
0252     if (!msg) {
0253         return set;
0254     }
0255 
0256     // FIXME: we really want "has any header" here, but the kmime api doesn't offer that yet
0257     if (msg->hasContent() || msg->hasHeader("Message-ID")) {
0258         set << MessagePart::Envelope << MessagePart::Header;
0259         if (!msg->body().isEmpty() || !msg->contents().isEmpty()) {
0260             set << MessagePart::Body;
0261         }
0262     }
0263     return set;
0264 }
0265 
0266 QString SerializerPluginMail::extractGid(const Item &item) const
0267 {
0268     if (!item.hasPayload<KMime::Message::Ptr>()) {
0269         return {};
0270     }
0271     const auto msg = item.payload<KMime::Message::Ptr>();
0272     KMime::Headers::MessageID *mid = msg->messageID(false);
0273     if (mid) {
0274         return mid->asUnicodeString();
0275     } else if (KMime::Headers::Base *uid = msg->headerByType("X-Akonotes-UID")) {
0276         return uid->asUnicodeString();
0277     }
0278     return {};
0279 }
0280 
0281 #include "moc_akonadi_serializer_mail.cpp"