/* SPDX-FileCopyrightText: 2007 Till Adam SPDX-License-Identifier: LGPL-2.0-or-later */ #include "akonadi_serializer_mail.h" #include "akonadi_serializer_mail_debug.h" #include "messageparts.h" #include #include #include #include #include #include using namespace Akonadi; using namespace KMime; QString StringPool::sharedValue(const QString &value) { QMutexLocker lock(&m_mutex); QSet::const_iterator it = m_pool.constFind(value); if (it != m_pool.constEnd()) { return *it; } m_pool.insert(value); return value; } template static void parseAddrList(const QVarLengthArray &addrList, T *hdr, int version, StringPool &pool) { hdr->clear(); const int count = addrList.count(); QVarLengthArray addr; for (int i = 0; i < count; ++i) { ImapParser::parseParenthesizedList(addrList[i], addr); if (addr.count() != 4) { qCWarning(AKONADI_SERIALIZER_MAIL_LOG) << "Error parsing envelope address field: " << addrList[i]; continue; } KMime::Types::Mailbox addrField; if (version == 0) { addrField.setNameFrom7Bit(addr[0]); } else if (version == 1) { addrField.setName(pool.sharedValue(QString::fromUtf8(addr[0]))); } KMime::Types::AddrSpec addrSpec; addrSpec.localPart = pool.sharedValue(QString::fromUtf8(addr[2])); addrSpec.domain = pool.sharedValue(QString::fromUtf8(addr[3])); addrField.setAddress(addrSpec); hdr->addAddress(addrField); } } template static void parseAddrList(QDataStream &stream, T *hdr, int version, StringPool &pool) { Q_UNUSED(version) hdr->clear(); int count = 0; stream >> count; for (int i = 0; i < count; ++i) { QString str; KMime::Types::Mailbox mbox; KMime::Types::AddrSpec addrSpec; stream >> str; mbox.setName(pool.sharedValue(str)); stream >> str; addrSpec.localPart = pool.sharedValue(str); stream >> str; addrSpec.domain = pool.sharedValue(str); mbox.setAddress(addrSpec); hdr->addAddress(mbox); } } bool SerializerPluginMail::deserialize(Item &item, const QByteArray &label, QIODevice &data, int version) { if (label != MessagePart::Body && label != MessagePart::Envelope && label != MessagePart::Header) { return false; } KMime::Message::Ptr msg; if (!item.hasPayload()) { auto m = new Message(); msg = KMime::Message::Ptr(m); item.setPayload(msg); } else { msg = item.payload(); } if (label == MessagePart::Body) { QByteArray buffer = data.readAll(); if (buffer.isEmpty()) { return true; } msg->setContent(buffer); msg->parse(); } else if (label == MessagePart::Header) { QByteArray buffer = data.readAll(); if (buffer.isEmpty()) { return true; } if (msg->body().isEmpty() && msg->contents().isEmpty()) { msg->setHead(buffer); msg->parse(); } } else if (label == MessagePart::Envelope) { if (version <= 1) { QByteArray buffer = data.readAll(); if (buffer.isEmpty()) { return true; } QVarLengthArray env; ImapParser::parseParenthesizedList(buffer, env); if (env.count() < 10) { qCWarning(AKONADI_SERIALIZER_MAIL_LOG) << "Akonadi KMime Deserializer: Got invalid envelope: " << buffer; return false; } Q_ASSERT(env.count() >= 10); // date msg->date()->from7BitString(env[0]); // subject msg->subject()->from7BitString(env[1]); // from QVarLengthArray addrList; ImapParser::parseParenthesizedList(env[2], addrList); if (!addrList.isEmpty()) { parseAddrList(addrList, msg->from(), version, m_stringPool); } // sender ImapParser::parseParenthesizedList(env[3], addrList); if (!addrList.isEmpty()) { parseAddrList(addrList, msg->sender(), version, m_stringPool); } // reply-to ImapParser::parseParenthesizedList(env[4], addrList); if (!addrList.isEmpty()) { parseAddrList(addrList, msg->replyTo(), version, m_stringPool); } // to ImapParser::parseParenthesizedList(env[5], addrList); if (!addrList.isEmpty()) { parseAddrList(addrList, msg->to(), version, m_stringPool); } // cc ImapParser::parseParenthesizedList(env[6], addrList); if (!addrList.isEmpty()) { parseAddrList(addrList, msg->cc(), version, m_stringPool); } // bcc ImapParser::parseParenthesizedList(env[7], addrList); if (!addrList.isEmpty()) { parseAddrList(addrList, msg->bcc(), version, m_stringPool); } // in-reply-to msg->inReplyTo()->from7BitString(env[8]); // message id msg->messageID()->from7BitString(env[9]); // references if (env.count() > 10) { msg->references()->from7BitString(env[10]); } } else if (version == 2) { QDataStream stream(&data); QDateTime dt; QString str; stream >> dt; msg->date()->setDateTime(dt); stream >> str; msg->subject()->fromUnicodeString(str, QByteArrayLiteral("UTF-8")); QString inReplyTo; stream >> inReplyTo; msg->inReplyTo()->fromUnicodeString(inReplyTo, QByteArrayLiteral("UTF-8")); stream >> str; msg->messageID()->fromUnicodeString(str, QByteArrayLiteral("UTF-8")); stream >> str; if (str == inReplyTo) { msg->references()->fromIdent(msg->inReplyTo()); } else { msg->references()->fromUnicodeString(str, QByteArrayLiteral("UTF-8")); } parseAddrList(stream, msg->from(), version, m_stringPool); parseAddrList(stream, msg->sender(), version, m_stringPool); parseAddrList(stream, msg->replyTo(), version, m_stringPool); parseAddrList(stream, msg->to(), version, m_stringPool); parseAddrList(stream, msg->cc(), version, m_stringPool); parseAddrList(stream, msg->bcc(), version, m_stringPool); if (stream.status() == QDataStream::ReadCorruptData || stream.status() == QDataStream::ReadPastEnd) { qCWarning(AKONADI_SERIALIZER_MAIL_LOG) << "Akonadi KMime Deserializer: Got invalid envelope"; return false; } } } return true; } template static void serializeAddrList(QDataStream &stream, T *hdr) { const KMime::Types::Mailbox::List mb = hdr->mailboxes(); stream << (qint32)mb.size(); for (const KMime::Types::Mailbox &mbox : mb) { stream << mbox.name() << mbox.addrSpec().localPart << mbox.addrSpec().domain; } } void SerializerPluginMail::serialize(const Item &item, const QByteArray &label, QIODevice &data, int &version) { version = 1; auto m = item.payload(); if (label == MessagePart::Body) { data.write(m->encodedContent()); } else if (label == MessagePart::Envelope) { version = 2; QDataStream stream(&data); stream << m->date()->dateTime() << m->subject()->asUnicodeString() << m->inReplyTo()->asUnicodeString() << m->messageID()->asUnicodeString() << m->references()->asUnicodeString(); serializeAddrList(stream, m->from()); serializeAddrList(stream, m->sender()); serializeAddrList(stream, m->replyTo()); serializeAddrList(stream, m->to()); serializeAddrList(stream, m->cc()); serializeAddrList(stream, m->bcc()); } else if (label == MessagePart::Header) { data.write(m->head()); } } QSet SerializerPluginMail::parts(const Item &item) const { QSet set; if (!item.hasPayload()) { return set; } auto msg = item.payload(); if (!msg) { return set; } // FIXME: we really want "has any header" here, but the kmime api doesn't offer that yet if (msg->hasContent() || msg->hasHeader("Message-ID")) { set << MessagePart::Envelope << MessagePart::Header; if (!msg->body().isEmpty() || !msg->contents().isEmpty()) { set << MessagePart::Body; } } return set; } QString SerializerPluginMail::extractGid(const Item &item) const { if (!item.hasPayload()) { return {}; } const auto msg = item.payload(); KMime::Headers::MessageID *mid = msg->messageID(false); if (mid) { return mid->asUnicodeString(); } else if (KMime::Headers::Base *uid = msg->headerByType("X-Akonotes-UID")) { return uid->asUnicodeString(); } return {}; } #include "moc_akonadi_serializer_mail.cpp"