KSMTP

sendjob.cpp
1/*
2 SPDX-FileCopyrightText: 2010 BetterInbox <contact@betterinbox.com>
3 SPDX-FileContributor: Christophe Laveault <christophe@betterinbox.com>
4 SPDX-FileContributor: Gregory Schlomoff <gregory.schlomoff@gmail.com>
5
6 SPDX-License-Identifier: LGPL-2.1-or-later
7*/
8
9#include "sendjob.h"
10#include "job_p.h"
11#include "ksmtp_debug.h"
12#include "serverresponse_p.h"
13
14#include <KLocalizedString>
15
16namespace KSmtp
17{
18class SendJobPrivate : public JobPrivate
19{
20public:
21 enum Status {
22 Idle,
23 SendingReturnPath,
24 SendingRecipients,
25 SendingData
26 };
27
28 SendJobPrivate(SendJob *job, Session *session, const QString &name)
29 : JobPrivate(session, name)
30 , q(job)
31 {
32 }
33
34 SendJob *const q;
35
36 void sendNextRecipient();
37 void addRecipients(const QStringList &rcpts);
38 bool prepare();
39
40 using MessagePart = struct {
41 QString contentType;
43 QByteArray content;
44 };
45
46 QString m_returnPath;
47 QStringList m_recipients;
48 QByteArray m_data;
49
50 QStringList m_recipientsCopy;
51 Status m_status = Idle;
52 bool m_dsn = false;
53};
54}
55
56using namespace KSmtp;
57
58SendJob::SendJob(Session *session)
59 : Job(*new SendJobPrivate(this, session, i18n("SendJob")))
60{
61}
62
63void SendJob::setFrom(const QString &from)
64{
65 Q_D(SendJob);
66 const auto start = from.indexOf(QLatin1Char('<'));
67 if (start > -1) {
68 const auto end = qMax(start, from.indexOf(QLatin1Char('>'), start));
69 d->m_returnPath = QStringLiteral("<%1>").arg(from.mid(start + 1, end - start - 1));
70 } else {
71 d->m_returnPath = QStringLiteral("<%1>").arg(from);
72 }
73}
74
76{
77 Q_D(SendJob);
78 d->addRecipients(to);
79}
80
82{
83 Q_D(SendJob);
84 d->addRecipients(cc);
85}
86
88{
89 Q_D(SendJob);
90 d->addRecipients(bcc);
91}
92
94{
95 Q_D(SendJob);
96 d->m_data = data;
97 // A line with a single dot would make SMTP think "end of message", so use two dots in that case,
98 // as per https://tools.ietf.org/html/rfc5321#section-4.5.2
99 d->m_data.replace("\r\n.", "\r\n..");
100}
101
102void SendJob::doStart()
103{
104 Q_D(SendJob);
105
106 if (!d->prepare()) {
107 setError(KJob::UserDefinedError);
108 setErrorText(i18n("Could not send the message because either the sender or recipient field is missing or invalid"));
109 emitResult();
110 return;
111 }
112
113 const int sizeLimit = session()->sizeLimit();
114 if (sizeLimit > 0 && size() > sizeLimit) {
115 setError(KJob::UserDefinedError);
116 setErrorText(i18n("Could not send the message because it exceeds the maximum allowed size of %1 bytes. (Message size: %2 bytes.)", sizeLimit, size()));
117 emitResult();
118 return;
119 }
120
121 d->m_status = SendJobPrivate::SendingReturnPath;
122 sendCommand("MAIL FROM:" + d->m_returnPath.toUtf8());
123}
124
125void SendJob::handleResponse(const ServerResponse &r)
126{
127 Q_D(SendJob);
128
129 // Handle server errors
130 handleErrors(r);
131
132 switch (d->m_status) {
133 case SendJobPrivate::Idle:
134 // TODO: anything to do here?
135 break;
136
137 case SendJobPrivate::SendingReturnPath:
138
139 // Expected response: server agreement
140 if (r.isCode(25)) {
141 d->m_status = SendJobPrivate::SendingRecipients;
142 d->sendNextRecipient();
143 }
144 break;
145
146 case SendJobPrivate::SendingRecipients:
147
148 // Expected response: server agreement
149 if (r.isCode(25)) {
150 if (d->m_recipientsCopy.isEmpty()) {
151 sendCommand("DATA");
152 d->m_status = SendJobPrivate::SendingData;
153 } else {
154 d->sendNextRecipient();
155 }
156 }
157 break;
158
159 case SendJobPrivate::SendingData:
160
161 // Expected responses:
162 // 354: Go ahead sending data
163 if (r.isCode(354)) {
164 sendCommand(d->m_data);
165 sendCommand("\r\n.");
166 }
167
168 // 25x: Data received correctly
169 if (r.isCode(25)) {
170 emitResult();
171 }
172 break;
173 }
174}
175
176void SendJobPrivate::sendNextRecipient()
177{
178 const bool dsnSupport = m_session->allowsDsn() ? m_dsn : false;
179 // qDebug() << " void SendJobPrivate::sendNextRecipient()" << m_session->allowsDsn() << " dsnSupport " << dsnSupport;
180 q->sendCommand("RCPT TO:<" + m_recipientsCopy.takeFirst().toUtf8() + '>' + (dsnSupport ? " NOTIFY=success,failure" : ""));
181}
182
183void SendJobPrivate::addRecipients(const QStringList &rcpts)
184{
185 for (const auto &rcpt : rcpts) {
186 if (rcpt.isEmpty()) {
187 continue;
188 }
189
190 const int start = rcpt.indexOf(QLatin1Char('<'));
191 if (start > -1) {
192 const int end = qMax(start, rcpt.indexOf(QLatin1Char('>'), start));
193 m_recipients.push_back(rcpt.mid(start + 1, end - start - 1));
194 } else {
195 m_recipients.push_back(rcpt);
196 }
197 }
198}
199
200bool SendJobPrivate::prepare()
201{
202 if (m_data.isEmpty()) {
203 qCWarning(KSMTP_LOG) << "A message has to be set before starting a SendJob";
204 return false;
205 }
206
207 m_recipientsCopy = m_recipients;
208
209 if (m_recipients.isEmpty()) {
210 qCWarning(KSMTP_LOG) << "Message has no recipients";
211 return false;
212 }
213
214 return true;
215}
216
217int SendJob::size() const
218{
219 Q_D(const SendJob);
220
221 return d->m_data.size();
222}
223
225{
226 Q_D(SendJob);
227 d->m_dsn = enabled;
228}
229
230#include "moc_sendjob.cpp"
void setErrorText(const QString &errorText)
void emitResult()
void setError(int errorCode)
The Job class.
Definition job.h:25
The SendJob class.
Definition sendjob.h:22
void setCc(const QStringList &cc)
Add recipients.
Definition sendjob.cpp:81
void setBcc(const QStringList &bcc)
Add recipients.
Definition sendjob.cpp:87
void setData(const QByteArray &data)
Set the actual message data.
Definition sendjob.cpp:93
void setDeliveryStatusNotification(bool b)
Set Delivery Status Notification.
Definition sendjob.cpp:224
void setFrom(const QString &from)
Set the sender email address.
Definition sendjob.cpp:63
int size() const
Returns size of the encoded message data.
Definition sendjob.cpp:217
void setTo(const QStringList &to)
Add recipients.
Definition sendjob.cpp:75
int sizeLimit() const
Returns the maximum message size in bytes that the server accepts.
Definition session.cpp:161
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18n(const char *text, const TYPE &arg...)
QString name(GameStandardAction id)
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
bool isEmpty() const const
QByteArray & replace(QByteArrayView before, QByteArrayView after)
bool isEmpty() const const
void push_back(parameter_type value)
value_type takeFirst()
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
QString mid(qsizetype position, qsizetype n) const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:37:35 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.