1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
2 Copyright (C) 2012 Peter Amidon <peter@picnicpark.org>
4 This file is part of the Trojita Qt IMAP e-mail client,
5 http://trojita.flaska.net/
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of
10 the License or (at your option) version 3 or any later version
11 accepted by the membership of KDE e.V. (or its successor approved
12 by the membership of KDE e.V.), which shall act as a proxy
13 defined in Section 14 of version 3 of the license.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include <QCoreApplication>
26 #include "Composer/Submission.h"
27 #include "Composer/MessageComposer.h"
28 #include "Imap/Model/Model.h"
29 #include "Imap/Tasks/AppendTask.h"
30 #include "Imap/Tasks/GenUrlAuthTask.h"
31 #include "Imap/Tasks/UidSubmitTask.h"
32 #include "MSA/Sendmail.h"
36 const int PROGRESS_MAX
= 1000;
37 // We're very likely almost half-way there -- let's call it 45%
38 const int PROGRESS_SAVING_DONE
= PROGRESS_MAX
* 0.45;
39 // Updating flags might take roughly as much time as the URLAUTH
40 const int PROGRESS_DELIVERY_DONE
= PROGRESS_MAX
* 0.95;
42 const int PROGRESS_DELIVERY_START_WITHOUT_SAVING
= PROGRESS_MAX
* 0.10;
43 const int PROGRESS_DELIVERY_START_WITH_SAVING
= PROGRESS_MAX
* 0.5;
49 QString
submissionProgressToString(const Submission::SubmissionProgress progress
)
52 case Submission::STATE_INIT
:
53 return QStringLiteral("INIT");
54 case Submission::STATE_BUILDING_MESSAGE
:
55 return QStringLiteral("BUILDING_MESSAGE");
56 case Submission::STATE_SAVING
:
57 return QStringLiteral("SAVING");
58 case Submission::STATE_PREPARING_URLAUTH
:
59 return QStringLiteral("PREPARING_URLAUTH");
60 case Submission::STATE_SUBMITTING
:
61 return QStringLiteral("SUBMITTING");
62 case Submission::STATE_UPDATING_FLAGS
:
63 return QStringLiteral("UPDATING_FLAGS");
64 case Submission::STATE_SENT
:
65 return QStringLiteral("SENT");
66 case Submission::STATE_FAILED
:
67 return QStringLiteral("FAILED");
69 return QStringLiteral("[unknown: %1]").arg(QString::number(static_cast<int>(progress
)));
72 Submission::Submission(QObject
*parent
, std::shared_ptr
<Composer::AbstractComposer
> composer
,
73 Imap::Mailbox::Model
*model
, MSA::MSAFactory
*msaFactory
, const QString
&accountId
)
75 , m_appendUidReceived(false)
76 , m_appendUidValidity(0)
78 , m_genUrlAuthReceived(false)
79 , m_saveToSentFolder(false)
81 , m_useImapSubmit(false)
83 , m_msaMaximalProgress(0)
86 , m_msaFactory(msaFactory
)
87 , m_accountId(accountId
)
88 , m_updateReplyingToMessageFlagsTask(0)
89 , m_updateForwardingMessageFlagsTask(0)
91 m_source
->setPreloadEnabled(shouldBuildMessageLocally());
94 Submission::~Submission() = default;
96 QString
Submission::accountId() const
101 void Submission::changeConnectionState(const SubmissionProgress state
)
103 emit
logged(Common::LOG_SUBMISSION
, QStringLiteral("Submission"), QStringLiteral("Progress: %1 -> %2").arg(
104 submissionProgressToString(m_state
), submissionProgressToString(state
)));
107 // Now broadcast a human-readable message and update the progress dialog
113 emit
updateStatusMessage(tr("Preparing to send"));
115 case STATE_BUILDING_MESSAGE
:
118 emit
updateStatusMessage(tr("Creating message"));
123 emit
updateStatusMessage(tr("Saving to the sent folder"));
125 case STATE_PREPARING_URLAUTH
:
126 emit
progressMax(PROGRESS_MAX
);
127 emit
progress(PROGRESS_SAVING_DONE
);
128 emit
updateStatusMessage(tr("Preparing message for delivery"));
130 case STATE_SUBMITTING
:
131 emit
progressMax(PROGRESS_MAX
);
132 emit
progress(m_saveToSentFolder
? PROGRESS_DELIVERY_START_WITH_SAVING
: PROGRESS_DELIVERY_START_WITHOUT_SAVING
);
133 emit
updateStatusMessage(tr("Submitting message"));
135 case STATE_UPDATING_FLAGS
:
136 emit
progressMax(PROGRESS_MAX
);
137 emit
progress(PROGRESS_DELIVERY_DONE
);
138 emit
updateStatusMessage(tr("Updating message keywords"));
141 emit
progressMax(PROGRESS_MAX
);
142 emit
progress(PROGRESS_MAX
);
143 emit
updateStatusMessage(tr("Message sent"));
146 // revert to the busy indicator
150 emit
updateStatusMessage(tr("Sending failed"));
155 void Submission::setImapOptions(const bool saveToSentFolder
, const QString
&sentFolderName
,
156 const QString
&hostname
, const QString
&username
, const bool useImapSubmit
)
158 m_saveToSentFolder
= saveToSentFolder
;
159 m_sentFolderName
= sentFolderName
;
160 m_imapHostname
= hostname
;
161 m_imapUsername
= username
;
162 m_useImapSubmit
= useImapSubmit
;
163 m_source
->setPreloadEnabled(shouldBuildMessageLocally());
166 void Submission::setSmtpOptions(const bool useBurl
, const QString
&smtpUsername
)
169 if (m_useBurl
&& !m_model
->isGenUrlAuthSupported()) {
170 emit
logged(Common::LOG_SUBMISSION
, QStringLiteral("Submission"), QStringLiteral("Cannot BURL without the URLAUTH extension"));
173 m_smtpUsername
= smtpUsername
;
174 m_source
->setPreloadEnabled(shouldBuildMessageLocally());
177 void Submission::send()
180 gotError(tr("The IMAP connection has disappeared. "
181 "You'll have close the composer, save the draft and re-open it later. "
182 "The attachments will have to be added later. Sorry for the trouble, "
183 "please see <a href=\"https://projects.flaska.net/issues/640\">https://projects.flaska.net/issues/640</a> "
188 // this double-updating is needed in case the same Submission attempts to send a message more than once
189 changeConnectionState(STATE_INIT
);
190 changeConnectionState(STATE_BUILDING_MESSAGE
);
192 if (shouldBuildMessageLocally() && !m_source
->isReadyForSerialization()) {
193 // we have to wait until the data arrive
194 // FIXME: relax this to wait here
195 gotError(tr("Some data are not available yet"));
197 slotMessageDataAvailable();
202 void Submission::slotMessageDataAvailable()
204 m_rawMessageData
.clear();
205 QBuffer
buf(&m_rawMessageData
);
206 buf
.open(QIODevice::WriteOnly
);
207 QString errorMessage
;
208 QList
<Imap::Mailbox::CatenatePair
> catenateable
;
210 if (shouldBuildMessageLocally() && !m_source
->asRawMessage(&buf
, &errorMessage
)) {
211 gotError(tr("Cannot send right now -- saving failed:\n %1").arg(errorMessage
));
214 if (m_model
->isCatenateSupported() && !m_source
->asCatenateData(catenateable
, &errorMessage
)) {
215 gotError(tr("Cannot send right now -- saving (CATENATE) failed:\n %1").arg(errorMessage
));
219 if (m_saveToSentFolder
) {
221 m_appendUidReceived
= false;
222 m_genUrlAuthReceived
= false;
224 changeConnectionState(STATE_SAVING
);
225 QPointer
<Imap::Mailbox::AppendTask
> appendTask
= 0;
227 if (m_model
->isCatenateSupported()) {
228 // FIXME: without UIDPLUS, there isn't much point in $SubmitPending...
229 appendTask
= QPointer
<Imap::Mailbox::AppendTask
>(
230 m_model
->appendIntoMailbox(
233 QStringList() << QStringLiteral("\\Seen"),
234 m_source
->timestamp()));
236 // FIXME: without UIDPLUS, there isn't much point in $SubmitPending...
237 appendTask
= QPointer
<Imap::Mailbox::AppendTask
>(
238 m_model
->appendIntoMailbox(
241 QStringList() << QStringLiteral("\\Seen"),
242 m_source
->timestamp()));
245 Q_ASSERT(appendTask
);
246 connect(appendTask
.data(), &Imap::Mailbox::AppendTask::appendUid
, this, &Submission::slotAppendUidKnown
);
247 connect(appendTask
.data(), &Imap::Mailbox::ImapTask::completed
, this, &Submission::slotAppendSucceeded
);
248 connect(appendTask
.data(), &Imap::Mailbox::ImapTask::failed
, this, &Submission::slotAppendFailed
);
254 void Submission::slotAskForUrl()
256 Q_ASSERT(m_appendUidReceived
&& m_useBurl
);
257 changeConnectionState(STATE_PREPARING_URLAUTH
);
258 Imap::Mailbox::GenUrlAuthTask
*genUrlAuthTask
= QPointer
<Imap::Mailbox::GenUrlAuthTask
>(
259 m_model
->generateUrlAuthForMessage(m_imapHostname
,
260 killDomainPartFromString(m_imapUsername
),
262 m_appendUidValidity
, m_appendUid
, QString(),
263 QStringLiteral("submit+%1").arg(
264 killDomainPartFromString(m_smtpUsername
))
266 connect(genUrlAuthTask
, &Imap::Mailbox::GenUrlAuthTask::gotAuth
, this, &Submission::slotGenUrlAuthReceived
);
267 connect(genUrlAuthTask
, &Imap::Mailbox::ImapTask::failed
, this, &Submission::gotError
);
270 void Submission::slotInvokeMsaNow()
272 changeConnectionState(STATE_SUBMITTING
);
273 MSA::AbstractMSA
*msa
= m_msaFactory
->create(this);
274 connect(msa
, &MSA::AbstractMSA::progressMax
, this, &Submission::onMsaProgressMaxChanged
);
275 connect(msa
, &MSA::AbstractMSA::progress
, this, &Submission::onMsaProgressCurrentChanged
);
276 connect(msa
, &MSA::AbstractMSA::sent
, this, &Submission::sent
);
277 connect(msa
, &MSA::AbstractMSA::error
, this, &Submission::gotError
);
278 connect(msa
, &MSA::AbstractMSA::passwordRequested
, this, &Submission::passwordRequested
);
279 connect(msa
, &MSA::AbstractMSA::logged
, this, &Submission::logged
);
280 connect(this, &Submission::gotPassword
, msa
, &MSA::AbstractMSA::setPassword
);
281 connect(this, &Submission::canceled
, msa
, &MSA::AbstractMSA::cancel
);
283 if (m_useImapSubmit
&& msa
->supportsImapSending() && m_appendUidReceived
) {
284 Imap::Mailbox::UidSubmitOptionsList options
;
285 options
.append(qMakePair
<QByteArray
,QVariant
>("FROM", m_source
->rawFromAddress()));
286 Q_FOREACH(const QByteArray
&recipient
, m_source
->rawRecipientAddresses()) {
287 options
.append(qMakePair
<QByteArray
,QVariant
>("RECIPIENT", recipient
));
289 msa
->sendImap(m_sentFolderName
, m_appendUidValidity
, m_appendUid
, options
);
290 } else if (m_genUrlAuthReceived
&& m_useBurl
) {
291 msa
->sendBurl(m_source
->rawFromAddress(), m_source
->rawRecipientAddresses(), m_urlauth
.toUtf8());
293 msa
->sendMail(m_source
->rawFromAddress(), m_source
->rawRecipientAddresses(), m_rawMessageData
);
297 void Submission::setPassword(const QString
&password
)
299 emit
gotPassword(password
);
302 void Submission::cancelPassword()
307 void Submission::gotError(const QString
&error
)
309 emit
logged(Common::LogKind::LOG_SUBMISSION
, QStringLiteral("Submission"), QStringLiteral("Error: ") + error
);
310 changeConnectionState(STATE_FAILED
);
314 void Submission::sent()
316 if (m_source
->replyingToMessage().isValid()) {
317 m_updateReplyingToMessageFlagsTask
= m_model
->setMessageFlags(QModelIndexList() << m_source
->replyingToMessage(),
318 QStringLiteral("\\Answered"), Imap::Mailbox::FLAG_ADD
);
319 connect(m_updateReplyingToMessageFlagsTask
, &Imap::Mailbox::ImapTask::completed
,
320 this, &Submission::onUpdatingFlagsOfReplyingToSucceded
);
321 connect(m_updateReplyingToMessageFlagsTask
, &Imap::Mailbox::ImapTask::failed
,
322 this, &Submission::onUpdatingFlagsOfReplyingToFailed
);
323 changeConnectionState(STATE_UPDATING_FLAGS
);
324 } else if (m_source
->forwardingMessage().isValid()) {
325 m_updateForwardingMessageFlagsTask
= m_model
->setMessageFlags(QModelIndexList() << m_source
->forwardingMessage(),
326 QStringLiteral("$Forwarded"), Imap::Mailbox::FLAG_ADD
);
327 connect(m_updateForwardingMessageFlagsTask
, &Imap::Mailbox::ImapTask::completed
,
328 this, &Submission::onUpdatingFlagsOfForwardingSucceeded
);
329 connect(m_updateForwardingMessageFlagsTask
, &Imap::Mailbox::ImapTask::failed
,
330 this, &Submission::onUpdatingFlagsOfForwardingFailed
);
331 changeConnectionState(STATE_UPDATING_FLAGS
);
333 changeConnectionState(STATE_SENT
);
337 if (m_appendUidReceived
) {
338 // FIXME: check the UIDVALIDITY!!!
339 // FIXME: doesn't work at all; the messageIndexByUid() only works on already selected mailboxes
340 QModelIndex message
= m_mainWindow
->imapModel()->
341 messageIndexByUid(QSettings().value(Common::SettingsNames::composerImapSentKey
, QStringLiteral("Sent")).toString(), m_appendUid
);
342 if (message
.isValid()) {
343 m_mainWindow
->imapModel()->setMessageFlags(QModelIndexList() << message
,
344 QLatin1String("\\Seen $Submitted"), Imap::Mailbox::FLAG_USE_THESE
);
349 // FIXME: move back to the currently selected mailbox
352 /** @short Remember the APPENDUID as reported by the APPEND operation */
353 void Submission::slotAppendUidKnown(const uint uidValidity
, const uint uid
)
355 m_appendUidValidity
= uidValidity
;
359 void Submission::slotAppendFailed(const QString
&error
)
361 gotError(tr("APPEND failed: %1").arg(error
));
364 void Submission::slotAppendSucceeded()
366 if (m_appendUid
&& m_appendUidValidity
) {
367 // Only ever consider valid UIDVALIDITY/UID pair
368 m_appendUidReceived
= true;
377 emit
logged(Common::LogKind::LOG_SUBMISSION
, QStringLiteral("Submission"),
378 QStringLiteral("APPEND does not contain APPENDUID or UIDVALIDITY, cannot use BURL or the SUBMIT command"));
383 /** @short Remember the GENURLAUTH response */
384 void Submission::slotGenUrlAuthReceived(const QString
&url
)
387 if (!m_urlauth
.isEmpty()) {
388 m_genUrlAuthReceived
= true;
391 gotError(tr("The URLAUTH response does not contain a proper URL"));
395 /** @short Remove the "@domain" from a string */
396 QString
Submission::killDomainPartFromString(const QString
&s
)
398 return s
.split(QLatin1Char('@'))[0];
401 /** @short Return true if the message payload shall be built locally */
402 bool Submission::shouldBuildMessageLocally() const
404 if (!m_useImapSubmit
) {
405 // sending via SMTP or Sendmail
406 // Unless all of URLAUTH, CATENATE and BURL is present and enabled, we will still have to download the data in the end
407 return ! (m_useBurl
&& m_model
->isCatenateSupported() && m_model
->isGenUrlAuthSupported());
409 return ! m_model
->isCatenateSupported();
413 void Submission::onUpdatingFlagsOfReplyingToSucceded()
415 m_updateReplyingToMessageFlagsTask
= 0;
416 changeConnectionState(STATE_SENT
);
420 void Submission::onUpdatingFlagsOfReplyingToFailed()
422 m_updateReplyingToMessageFlagsTask
= 0;
423 emit
logged(Common::LogKind::LOG_OTHER
, QStringLiteral("Submission"),
424 QStringLiteral("Cannot update flags of the message we replied to -- interesting, but we cannot do anything at this point anyway"));
425 changeConnectionState(STATE_SENT
);
429 void Submission::onUpdatingFlagsOfForwardingSucceeded()
431 m_updateForwardingMessageFlagsTask
= 0;
432 changeConnectionState(STATE_SENT
);
436 void Submission::onUpdatingFlagsOfForwardingFailed()
438 m_updateForwardingMessageFlagsTask
= 0;
439 emit
logged(Common::LogKind::LOG_OTHER
, QStringLiteral("Submission"),
440 QStringLiteral("Cannot update flags of the message we forwarded -- interesting, but we cannot do anything at this point anyway"));
441 changeConnectionState(STATE_SENT
);
445 void Submission::onMsaProgressCurrentChanged(const int value
)
447 if (m_msaMaximalProgress
> 0) {
448 // prevent division by zero or performing operations which do not make any sense
449 int low
= m_saveToSentFolder
? PROGRESS_DELIVERY_START_WITH_SAVING
: PROGRESS_DELIVERY_START_WITHOUT_SAVING
;
450 int high
= PROGRESS_DELIVERY_DONE
;
451 emit
progress(1.0 * value
/ m_msaMaximalProgress
* (high
- low
) + low
);
455 void Submission::onMsaProgressMaxChanged(const int max
)
457 m_msaMaximalProgress
= max
;