File indexing completed on 2024-05-12 05:21:35

0001 /*
0002   SPDX-FileCopyrightText: 2010 BetterInbox <contact@betterinbox.com>
0003   SPDX-FileContributor: Christophe Laveault <christophe@betterinbox.com>
0004   SPDX-FileContributor: Gregory Schlomoff <gregory.schlomoff@gmail.com>
0005 
0006   SPDX-License-Identifier: LGPL-2.1-or-later
0007 */
0008 
0009 #include "sendjob.h"
0010 #include "job_p.h"
0011 #include "ksmtp_debug.h"
0012 #include "serverresponse_p.h"
0013 
0014 #include <KLocalizedString>
0015 
0016 namespace KSmtp
0017 {
0018 class SendJobPrivate : public JobPrivate
0019 {
0020 public:
0021     enum Status { Idle, SendingReturnPath, SendingRecipients, SendingData };
0022 
0023     SendJobPrivate(SendJob *job, Session *session, const QString &name)
0024         : JobPrivate(session, name)
0025         , q(job)
0026     {
0027     }
0028 
0029     SendJob *const q;
0030 
0031     void sendNextRecipient();
0032     void addRecipients(const QStringList &rcpts);
0033     bool prepare();
0034 
0035     using MessagePart = struct {
0036         QString contentType;
0037         QString name;
0038         QByteArray content;
0039     };
0040 
0041     QString m_returnPath;
0042     QStringList m_recipients;
0043     QByteArray m_data;
0044 
0045     QStringList m_recipientsCopy;
0046     Status m_status = Idle;
0047     bool m_dsn = false;
0048 };
0049 }
0050 
0051 using namespace KSmtp;
0052 
0053 SendJob::SendJob(Session *session)
0054     : Job(*new SendJobPrivate(this, session, i18n("SendJob")))
0055 {
0056 }
0057 
0058 void SendJob::setFrom(const QString &from)
0059 {
0060     Q_D(SendJob);
0061     const auto start = from.indexOf(QLatin1Char('<'));
0062     if (start > -1) {
0063         const auto end = qMax(start, from.indexOf(QLatin1Char('>'), start));
0064         d->m_returnPath = QStringLiteral("<%1>").arg(from.mid(start + 1, end - start - 1));
0065     } else {
0066         d->m_returnPath = QStringLiteral("<%1>").arg(from);
0067     }
0068 }
0069 
0070 void SendJob::setTo(const QStringList &to)
0071 {
0072     Q_D(SendJob);
0073     d->addRecipients(to);
0074 }
0075 
0076 void SendJob::setCc(const QStringList &cc)
0077 {
0078     Q_D(SendJob);
0079     d->addRecipients(cc);
0080 }
0081 
0082 void SendJob::setBcc(const QStringList &bcc)
0083 {
0084     Q_D(SendJob);
0085     d->addRecipients(bcc);
0086 }
0087 
0088 void SendJob::setData(const QByteArray &data)
0089 {
0090     Q_D(SendJob);
0091     d->m_data = data;
0092     // A line with a single dot would make SMTP think "end of message", so use two dots in that case,
0093     // as per https://tools.ietf.org/html/rfc5321#section-4.5.2
0094     d->m_data.replace("\r\n.", "\r\n..");
0095 }
0096 
0097 void SendJob::doStart()
0098 {
0099     Q_D(SendJob);
0100 
0101     if (!d->prepare()) {
0102         setError(KJob::UserDefinedError);
0103         setErrorText(i18n("Could not send the message because either the sender or recipient field is missing or invalid"));
0104         emitResult();
0105         return;
0106     }
0107 
0108     const int sizeLimit = session()->sizeLimit();
0109     if (sizeLimit > 0 && size() > sizeLimit) {
0110         setError(KJob::UserDefinedError);
0111         setErrorText(i18n("Could not send the message because it exceeds the maximum allowed size of %1 bytes. (Message size: %2 bytes.)", sizeLimit, size()));
0112         emitResult();
0113         return;
0114     }
0115 
0116     d->m_status = SendJobPrivate::SendingReturnPath;
0117     sendCommand("MAIL FROM:" + d->m_returnPath.toUtf8());
0118 }
0119 
0120 void SendJob::handleResponse(const ServerResponse &r)
0121 {
0122     Q_D(SendJob);
0123 
0124     // Handle server errors
0125     handleErrors(r);
0126 
0127     switch (d->m_status) {
0128     case SendJobPrivate::Idle:
0129         // TODO: anything to do here?
0130         break;
0131 
0132     case SendJobPrivate::SendingReturnPath:
0133 
0134         // Expected response: server agreement
0135         if (r.isCode(25)) {
0136             d->m_status = SendJobPrivate::SendingRecipients;
0137             d->sendNextRecipient();
0138         }
0139         break;
0140 
0141     case SendJobPrivate::SendingRecipients:
0142 
0143         // Expected response: server agreement
0144         if (r.isCode(25)) {
0145             if (d->m_recipientsCopy.isEmpty()) {
0146                 sendCommand("DATA");
0147                 d->m_status = SendJobPrivate::SendingData;
0148             } else {
0149                 d->sendNextRecipient();
0150             }
0151         }
0152         break;
0153 
0154     case SendJobPrivate::SendingData:
0155 
0156         // Expected responses:
0157         // 354: Go ahead sending data
0158         if (r.isCode(354)) {
0159             sendCommand(d->m_data);
0160             sendCommand("\r\n.");
0161         }
0162 
0163         // 25x: Data received correctly
0164         if (r.isCode(25)) {
0165             emitResult();
0166         }
0167         break;
0168     }
0169 }
0170 
0171 void SendJobPrivate::sendNextRecipient()
0172 {
0173     const bool dsnSupport = m_session->allowsDsn() ? m_dsn : false;
0174     // qDebug() << " void SendJobPrivate::sendNextRecipient()" << m_session->allowsDsn() << " dsnSupport " << dsnSupport;
0175     q->sendCommand("RCPT TO:<" + m_recipientsCopy.takeFirst().toUtf8() + '>' + (dsnSupport ? " NOTIFY=success,failure" : ""));
0176 }
0177 
0178 void SendJobPrivate::addRecipients(const QStringList &rcpts)
0179 {
0180     for (const auto &rcpt : rcpts) {
0181         if (rcpt.isEmpty()) {
0182             continue;
0183         }
0184 
0185         const int start = rcpt.indexOf(QLatin1Char('<'));
0186         if (start > -1) {
0187             const int end = qMax(start, rcpt.indexOf(QLatin1Char('>'), start));
0188             m_recipients.push_back(rcpt.mid(start + 1, end - start - 1));
0189         } else {
0190             m_recipients.push_back(rcpt);
0191         }
0192     }
0193 }
0194 
0195 bool SendJobPrivate::prepare()
0196 {
0197     if (m_data.isEmpty()) {
0198         qCWarning(KSMTP_LOG) << "A message has to be set before starting a SendJob";
0199         return false;
0200     }
0201 
0202     m_recipientsCopy = m_recipients;
0203 
0204     if (m_recipients.isEmpty()) {
0205         qCWarning(KSMTP_LOG) << "Message has no recipients";
0206         return false;
0207     }
0208 
0209     return true;
0210 }
0211 
0212 int SendJob::size() const
0213 {
0214     Q_D(const SendJob);
0215 
0216     return d->m_data.size();
0217 }
0218 
0219 void SendJob::setDeliveryStatusNotification(bool enabled)
0220 {
0221     Q_D(SendJob);
0222     d->m_dsn = enabled;
0223 }
0224 
0225 #include "moc_sendjob.cpp"