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"