sendjob.cpp 5.14 KB
Newer Older
Gregory Schlomoff's avatar
Gregory Schlomoff committed
1
/*
2
3
4
  SPDX-FileCopyrightText: 2010 BetterInbox <contact@betterinbox.com>
  SPDX-FileContributor: Christophe Laveault <christophe@betterinbox.com>
  SPDX-FileContributor: Gregory Schlomoff <gregory.schlomoff@gmail.com>
Gregory Schlomoff's avatar
Gregory Schlomoff committed
5

6
  SPDX-License-Identifier: LGPL-2.1-or-later
Gregory Schlomoff's avatar
Gregory Schlomoff committed
7
8
9
10
*/

#include "sendjob.h"
#include "job_p.h"
Daniel Vrátil's avatar
Daniel Vrátil committed
11
#include "ksmtp_debug.h"
Laurent Montel's avatar
Laurent Montel committed
12
#include "serverresponse_p.h"
Daniel Vrátil's avatar
Daniel Vrátil committed
13
14

#include <KLocalizedString>
Gregory Schlomoff's avatar
Gregory Schlomoff committed
15

Laurent Montel's avatar
Laurent Montel committed
16
17
namespace KSmtp
{
Daniel Vrátil's avatar
Daniel Vrátil committed
18
19
class SendJobPrivate : public JobPrivate
{
Gregory Schlomoff's avatar
Gregory Schlomoff committed
20
public:
Laurent Montel's avatar
Laurent Montel committed
21
    enum Status { Idle, SendingReturnPath, SendingRecipients, SendingData };
Daniel Vrátil's avatar
Daniel Vrátil committed
22
23

    SendJobPrivate(SendJob *job, Session *session, const QString &name)
Laurent Montel's avatar
Laurent Montel committed
24
25
        : JobPrivate(session, name)
        , q(job)
Daniel Vrátil's avatar
Daniel Vrátil committed
26
27
28
    {
    }

Laurent Montel's avatar
Laurent Montel committed
29
    SendJob *const q;
Daniel Vrátil's avatar
Daniel Vrátil committed
30
31

    void sendNextRecipient();
32
    void addRecipients(const QStringList &rcpts);
Daniel Vrátil's avatar
Daniel Vrátil committed
33
34
    bool prepare();

Laurent Montel's avatar
Laurent Montel committed
35
    using MessagePart = struct {
Daniel Vrátil's avatar
Daniel Vrátil committed
36
37
38
        QString contentType;
        QString name;
        QByteArray content;
Laurent Montel's avatar
Laurent Montel committed
39
    };
Daniel Vrátil's avatar
Daniel Vrátil committed
40
41
42
43
44
45

    QString m_returnPath;
    QStringList m_recipients;
    QByteArray m_data;

    QStringList m_recipientsCopy;
Laurent Montel's avatar
Laurent Montel committed
46
    Status m_status = Idle;
Gregory Schlomoff's avatar
Gregory Schlomoff committed
47
48
49
50
51
52
};
}

using namespace KSmtp;

SendJob::SendJob(Session *session)
Daniel Vrátil's avatar
Daniel Vrátil committed
53
    : Job(*new SendJobPrivate(this, session, i18n("SendJob")))
Gregory Schlomoff's avatar
Gregory Schlomoff committed
54
55
56
{
}

57
58
59
void SendJob::setFrom(const QString &from)
{
    Q_D(SendJob);
Laurent Montel's avatar
Laurent Montel committed
60
    qDebug() << "void SendJob::setFrom(const QString &from) " << from;
61
62
63
64
65
66
67
    const auto start = from.indexOf(QLatin1Char('<'));
    if (start > -1) {
        const auto end = qMax(start, from.indexOf(QLatin1Char('>'), start));
        d->m_returnPath = QStringLiteral("<%1>").arg(from.mid(start + 1, end - start - 1));
    } else {
        d->m_returnPath = QStringLiteral("<%1>").arg(from);
    }
Laurent Montel's avatar
Laurent Montel committed
68
    qDebug() << "d->m_returnPath  " << d->m_returnPath;
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
}

void SendJob::setTo(const QStringList &to)
{
    Q_D(SendJob);
    d->addRecipients(to);
}

void SendJob::setCc(const QStringList &cc)
{
    Q_D(SendJob);
    d->addRecipients(cc);
}

void SendJob::setBcc(const QStringList &bcc)
{
    Q_D(SendJob);
    d->addRecipients(bcc);
}

void SendJob::setData(const QByteArray &data)
{
    Q_D(SendJob);
    d->m_data = data;
93
94
    // A line with a single dot would make SMTP think "end of message", so use two dots in that case,
    // as per https://tools.ietf.org/html/rfc5321#section-4.5.2
95
    d->m_data.replace("\r\n.", "\r\n..");
96
97
}

Gregory Schlomoff's avatar
Gregory Schlomoff committed
98
99
void SendJob::doStart()
{
Daniel Vrátil's avatar
Daniel Vrátil committed
100
    Q_D(SendJob);
Allen Winter's avatar
Allen Winter committed
101

Daniel Vrátil's avatar
Daniel Vrátil committed
102
103
104
105
106
    if (!d->prepare()) {
        setError(KJob::UserDefinedError);
        setErrorText(i18n("Could not send the message because either the sender or recipient field is missing or invalid"));
        emitResult();
        return;
Gregory Schlomoff's avatar
Gregory Schlomoff committed
107
108
    }

Daniel Vrátil's avatar
Daniel Vrátil committed
109
110
111
112
113
114
    int sizeLimit = session()->sizeLimit();
    if (sizeLimit > 0 && size() > sizeLimit) {
        setError(KJob::UserDefinedError);
        setErrorText(i18n("Could not send the message because it exceeds the maximum allowed size of %1 bytes. (Message size: %2 bytes.)", sizeLimit, size()));
        emitResult();
        return;
Gregory Schlomoff's avatar
Gregory Schlomoff committed
115
116
    }

Daniel Vrátil's avatar
Daniel Vrátil committed
117
118
119
120
121
122
123
124
125
126
127
128
129
    d->m_status = SendJobPrivate::SendingReturnPath;
    sendCommand("MAIL FROM:" + d->m_returnPath.toUtf8());
}

void SendJob::handleResponse(const ServerResponse &r)
{
    Q_D(SendJob);

    // Handle server errors
    handleErrors(r);

    switch (d->m_status) {
    case SendJobPrivate::Idle:
Laurent Montel's avatar
Laurent Montel committed
130
        // TODO: anything to do here?
Daniel Vrátil's avatar
Daniel Vrátil committed
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
        break;

    case SendJobPrivate::SendingReturnPath:

        // Expected response: server agreement
        if (r.isCode(25)) {
            d->m_status = SendJobPrivate::SendingRecipients;
            d->sendNextRecipient();
        }
        break;

    case SendJobPrivate::SendingRecipients:

        // Expected response: server agreement
        if (r.isCode(25)) {
            if (d->m_recipientsCopy.isEmpty()) {
                sendCommand("DATA");
                d->m_status = SendJobPrivate::SendingData;
            } else {
                d->sendNextRecipient();
            }
        }
        break;

    case SendJobPrivate::SendingData:

        // Expected responses:
        // 354: Go ahead sending data
        if (r.isCode(354)) {
            sendCommand(d->m_data);
            sendCommand("\r\n.");
        }

        // 25x: Data received correctly
        if (r.isCode(25)) {
            emitResult();
        }
        break;
Gregory Schlomoff's avatar
Gregory Schlomoff committed
169
170
171
172
173
    }
}

void SendJobPrivate::sendNextRecipient()
{
Daniel Vrátil's avatar
Daniel Vrátil committed
174
    q->sendCommand("RCPT TO:<" + m_recipientsCopy.takeFirst().toUtf8() + '>');
Gregory Schlomoff's avatar
Gregory Schlomoff committed
175
176
}

177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
void SendJobPrivate::addRecipients(const QStringList &rcpts)
{
    for (const auto &rcpt : rcpts) {
        if (rcpt.isEmpty()) {
            continue;
        }

        const int start = rcpt.indexOf(QLatin1Char('<'));
        if (start > -1) {
            const int end = qMax(start, rcpt.indexOf(QLatin1Char('>'), start));
            m_recipients.push_back(rcpt.mid(start + 1, end - start - 1));
        } else {
            m_recipients.push_back(rcpt);
        }
    }
}
Gregory Schlomoff's avatar
Gregory Schlomoff committed
193
194
195

bool SendJobPrivate::prepare()
{
196
    if (m_data.isEmpty()) {
Daniel Vrátil's avatar
Daniel Vrátil committed
197
198
        qCWarning(KSMTP_LOG) << "A message has to be set before starting a SendJob";
        return false;
Gregory Schlomoff's avatar
Gregory Schlomoff committed
199
    }
Daniel Vrátil's avatar
Daniel Vrátil committed
200
201

    m_recipientsCopy = m_recipients;
Gregory Schlomoff's avatar
Gregory Schlomoff committed
202

Daniel Vrátil's avatar
Daniel Vrátil committed
203
204
205
206
207
208
    if (m_recipients.isEmpty()) {
        qCWarning(KSMTP_LOG) << "Message has no recipients";
        return false;
    }

    return true;
Gregory Schlomoff's avatar
Gregory Schlomoff committed
209
210
211
212
}

int SendJob::size() const
{
Daniel Vrátil's avatar
Daniel Vrátil committed
213
214
    Q_D(const SendJob);

215
    return d->m_data.size();
Gregory Schlomoff's avatar
Gregory Schlomoff committed
216
}