File indexing completed on 2024-06-23 05:18:37

0001 /*
0002  * This file is part of KMail.
0003  * SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "akonadisender.h"
0009 
0010 #include "messagecomposer_debug.h"
0011 
0012 #include "MessageComposer/Util"
0013 #include "helper/messagehelper.h"
0014 #include "settings/messagecomposersettings.h"
0015 #include "utils/util_p.h"
0016 
0017 #include <Libkdepim/ProgressManager>
0018 
0019 #include <Akonadi/DispatcherInterface>
0020 #include <Akonadi/MessageQueueJob>
0021 #include <KEmailAddress>
0022 #include <KIdentityManagementCore/Identity>
0023 #include <KIdentityManagementCore/IdentityManager>
0024 #include <MailTransport/Transport>
0025 #include <MailTransport/TransportManager>
0026 #include <MessageCore/StringUtil>
0027 using namespace KMime::Types;
0028 using namespace KPIM;
0029 using namespace MessageComposer;
0030 
0031 static QStringList addrSpecListToStringList(const AddrSpecList &l, bool allowEmpty = false)
0032 {
0033     QStringList result;
0034     for (AddrSpecList::const_iterator it = l.constBegin(), end = l.constEnd(); it != end; ++it) {
0035         const QString s = (*it).asString();
0036         if (allowEmpty || !s.isEmpty()) {
0037             result.push_back(s);
0038         }
0039     }
0040     return result;
0041 }
0042 
0043 static void extractSenderToCCAndBcc(const KMime::Message::Ptr &aMsg, QString &sender, QStringList &to, QStringList &cc, QStringList &bcc)
0044 {
0045     sender = aMsg->sender()->asUnicodeString();
0046     if (aMsg->headerByType("X-KMail-Recipients")) {
0047         // extended BCC handling to prevent TOs and CCs from seeing
0048         // BBC information by looking at source of an OpenPGP encrypted mail
0049         to = addrSpecListToStringList(MessageHelper::extractAddrSpecs(aMsg, "X-KMail-Recipients"));
0050         aMsg->removeHeader("X-KMail-Recipients");
0051     } else if (aMsg->headerByType("Resent-To")) {
0052         to = addrSpecListToStringList(MessageHelper::extractAddrSpecs(aMsg, "Resent-To"));
0053         cc = addrSpecListToStringList(MessageHelper::extractAddrSpecs(aMsg, "Resent-Cc"));
0054         bcc = addrSpecListToStringList(MessageHelper::extractAddrSpecs(aMsg, "Resent-Bcc"));
0055     } else {
0056         to = addrSpecListToStringList(MessageHelper::extractAddrSpecs(aMsg, "To"));
0057         cc = addrSpecListToStringList(MessageHelper::extractAddrSpecs(aMsg, "Cc"));
0058         bcc = addrSpecListToStringList(MessageHelper::extractAddrSpecs(aMsg, "Bcc"));
0059     }
0060 }
0061 
0062 class MessageComposer::AkonadiSenderPrivate
0063 {
0064 public:
0065     AkonadiSenderPrivate() = default;
0066 
0067     QSet<KJob *> mPendingJobs;
0068     int mCustomTransportId = -1;
0069 };
0070 
0071 AkonadiSender::AkonadiSender(QObject *parent)
0072     : QObject(parent)
0073     , d(new MessageComposer::AkonadiSenderPrivate)
0074 {
0075 }
0076 
0077 AkonadiSender::~AkonadiSender() = default;
0078 
0079 bool AkonadiSender::doSend(const KMime::Message::Ptr &aMsg, short sendNow)
0080 {
0081     if (sendNow == -1) {
0082         sendNow = MessageComposer::MessageComposerSettings::self()->sendImmediate(); // -1 == use default setting
0083     }
0084     if (sendNow) {
0085         sendOrQueueMessage(aMsg, MessageComposer::MessageSender::SendImmediate);
0086     } else {
0087         sendOrQueueMessage(aMsg, MessageComposer::MessageSender::SendLater);
0088     }
0089     return true;
0090 }
0091 
0092 bool AkonadiSender::doSendQueued(int customTransportId)
0093 {
0094     qCDebug(MESSAGECOMPOSER_LOG) << "Sending queued message with custom transport:" << customTransportId;
0095     if (!MessageComposer::Util::sendMailDispatcherIsOnline()) {
0096         return false;
0097     }
0098 
0099     d->mCustomTransportId = customTransportId;
0100 
0101     auto dispatcher = new Akonadi::DispatcherInterface();
0102     if (d->mCustomTransportId == -1) {
0103         dispatcher->dispatchManually();
0104     } else {
0105         dispatcher->dispatchManualTransport(d->mCustomTransportId);
0106     }
0107     delete dispatcher;
0108     return true;
0109 }
0110 
0111 void AkonadiSender::sendOrQueueMessage(const KMime::Message::Ptr &message, MessageComposer::MessageSender::SendMethod method)
0112 {
0113     Q_ASSERT(message);
0114     qCDebug(MESSAGECOMPOSER_LOG) << "KMime::Message: \n[\n" << message->encodedContent().left(1000) << "\n]\n";
0115 
0116     auto qjob = new Akonadi::MessageQueueJob(this);
0117     if (message->hasHeader("X-KMail-FccDisabled")) {
0118         qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::Delete);
0119     } else if (auto hrd = message->headerByType("X-KMail-Fcc")) {
0120         qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::MoveToCollection);
0121         const int sentCollectionId = hrd->asUnicodeString().toInt();
0122         qjob->sentBehaviourAttribute().setMoveToCollection(Akonadi::Collection(sentCollectionId));
0123     } else if (auto hrd = message->headerByType("X-KMail-Identity")) {
0124         KIdentityManagementCore::IdentityManager *im = KIdentityManagementCore::IdentityManager::self();
0125         const QString identityStrId = hrd->asUnicodeString();
0126         const KIdentityManagementCore::Identity id = im->modifyIdentityForUoid(identityStrId.toUInt());
0127         const QString fccId = id.fcc();
0128         qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::MoveToCollection);
0129         const int sentCollectionId = fccId.toInt();
0130         qjob->sentBehaviourAttribute().setMoveToCollection(Akonadi::Collection(sentCollectionId));
0131     } else if (auto hrd = message->headerByType("X-KMail-Identity-Name")) {
0132         KIdentityManagementCore::IdentityManager *im = KIdentityManagementCore::IdentityManager::self();
0133         const QString identityStrName = hrd->asUnicodeString();
0134         const KIdentityManagementCore::Identity id = im->modifyIdentityForName(identityStrName);
0135         const QString fccId = id.fcc();
0136         qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::MoveToCollection);
0137         const int sentCollectionId = fccId.toInt();
0138         qjob->sentBehaviourAttribute().setMoveToCollection(Akonadi::Collection(sentCollectionId));
0139     } else {
0140         qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::MoveToDefaultSentCollection);
0141     }
0142     qjob->setMessage(message);
0143 
0144     // Get transport.
0145     int transportId = -1;
0146     if (d->mCustomTransportId != -1) {
0147         transportId = d->mCustomTransportId;
0148     } else {
0149         if (auto hrd = message->headerByType("X-KMail-Transport")) {
0150             transportId = hrd->asUnicodeString().toInt();
0151         }
0152     }
0153     const auto *transport = MailTransport::TransportManager::self()->transportById(transportId);
0154     if (!transport) {
0155         qCDebug(MESSAGECOMPOSER_LOG) << " No transport defined. Need to create it";
0156         qjob->deleteLater();
0157         return;
0158     }
0159 
0160     if ((method == MessageComposer::MessageSender::SendImmediate) && !MessageComposer::Util::sendMailDispatcherIsOnline()) {
0161         qjob->deleteLater();
0162         return;
0163     }
0164 
0165     qCDebug(MESSAGECOMPOSER_LOG) << "Using transport (" << transport->name() << "," << transport->id() << ")";
0166     qjob->transportAttribute().setTransportId(transport->id());
0167 
0168     // if we want to manually queue it for sending later, then do it
0169     if (method == MessageComposer::MessageSender::SendLater) {
0170         qjob->dispatchModeAttribute().setDispatchMode(Akonadi::DispatchModeAttribute::Manual);
0171     }
0172 
0173     // Get addresses.
0174     QStringList to;
0175     QStringList cc;
0176     QStringList bcc;
0177     QString from;
0178     extractSenderToCCAndBcc(message, from, to, cc, bcc);
0179     qjob->addressAttribute().setFrom(from);
0180     qjob->addressAttribute().setTo(to);
0181     qjob->addressAttribute().setCc(cc);
0182     qjob->addressAttribute().setBcc(bcc);
0183 
0184     if (qjob->addressAttribute().to().isEmpty()) {
0185         qCWarning(MESSAGECOMPOSER_LOG) << " Impossible to specify TO! It's a bug";
0186         qjob->deleteLater();
0187         return;
0188     }
0189 
0190     if (transport && transport->specifySenderOverwriteAddress()) {
0191         qjob->addressAttribute().setFrom(
0192             KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(transport->senderOverwriteAddress())));
0193     } else {
0194         qjob->addressAttribute().setFrom(KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(message->from()->asUnicodeString())));
0195     }
0196 
0197     MessageComposer::Util::addSendReplyForwardAction(message, qjob);
0198 
0199     MessageCore::StringUtil::removePrivateHeaderFields(message, false);
0200     message->assemble();
0201 
0202     // Queue the message.
0203     connect(qjob, &Akonadi::MessageQueueJob::result, this, &AkonadiSender::queueJobResult);
0204     d->mPendingJobs.insert(qjob);
0205     qjob->start();
0206     qCDebug(MESSAGECOMPOSER_LOG) << "QueueJob started.";
0207 
0208     // TODO potential problem:
0209     // The MDA finishes sending a message before I queue the next one, and
0210     // thinking it is finished, the progress item deletes itself.
0211     // Turn the MDA offline until everything is queued?
0212 }
0213 
0214 void AkonadiSender::queueJobResult(KJob *job)
0215 {
0216     Q_ASSERT(d->mPendingJobs.contains(job));
0217     d->mPendingJobs.remove(job);
0218 
0219     if (job->error()) {
0220         qCDebug(MESSAGECOMPOSER_LOG) << "QueueJob failed with error" << job->errorString();
0221     } else {
0222         qCDebug(MESSAGECOMPOSER_LOG) << "QueueJob success.";
0223     }
0224 }
0225 
0226 #include "moc_akonadisender.cpp"