1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
3 This file is part of the Trojita Qt IMAP e-mail client,
4 http://trojita.flaska.net/
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License as
8 published by the Free Software Foundation; either version 2 of
9 the License or (at your option) version 3 or any later version
10 accepted by the membership of KDE e.V. (or its successor approved
11 by the membership of KDE e.V.), which shall act as a proxy
12 defined in Section 14 of version 3 of the license.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include "ImapAccess.h"
28 #include "Common/MetaTypes.h"
29 #include "Common/Paths.h"
30 #include "Common/PortNumbers.h"
31 #include "Common/SettingsNames.h"
32 #include "Imap/Model/CombinedCache.h"
33 #include "Imap/Model/MemoryCache.h"
34 #include "Imap/Model/Utils.h"
35 #include "Imap/Network/MsgPartNetAccessManager.h"
36 #include "Streams/SocketFactory.h"
40 ImapAccess::ImapAccess(QObject
*parent
, QSettings
*settings
, const QString
&accountName
) :
41 QObject(parent
), m_settings(settings
), m_imapModel(0), m_mailboxModel(0), m_mailboxSubtreeModel(0), m_msgListModel(0),
42 m_visibleTasksModel(0), m_oneMessageModel(0), m_netWatcher(0), m_msgQNAM(0), m_port(0),
43 m_connectionMethod(Common::ConnectionMethod::Invalid
),
44 m_sslInfoIcon(Imap::Mailbox::CertificateUtils::NoIcon
),
45 m_accountName(accountName
)
47 Imap::migrateSettings(m_settings
);
48 m_server
= m_settings
->value(Common::SettingsNames::imapHostKey
).toString();
49 m_username
= m_settings
->value(Common::SettingsNames::imapUserKey
).toString();
50 if (m_settings
->value(Common::SettingsNames::imapMethodKey
).toString() == Common::SettingsNames::methodSSL
) {
51 m_connectionMethod
= Common::ConnectionMethod::NetDedicatedTls
;
52 } else if (m_settings
->value(Common::SettingsNames::imapMethodKey
).toString() == Common::SettingsNames::methodTCP
) {
53 m_connectionMethod
= m_settings
->value(Common::SettingsNames::imapStartTlsKey
).toBool() ?
54 Common::ConnectionMethod::NetStartTls
: Common::ConnectionMethod::NetCleartext
;
55 } else if (m_settings
->value(Common::SettingsNames::imapMethodKey
).toString() == Common::SettingsNames::methodProcess
) {
56 m_connectionMethod
= Common::ConnectionMethod::Process
;
58 m_port
= m_settings
->value(Common::SettingsNames::imapPortKey
, QVariant(0)).toInt();
60 switch (m_connectionMethod
) {
61 case Common::ConnectionMethod::NetCleartext
:
62 case Common::ConnectionMethod::NetStartTls
:
63 m_port
= Common::PORT_IMAP
;
65 case Common::ConnectionMethod::NetDedicatedTls
:
66 m_port
= Common::PORT_IMAPS
;
68 case Common::ConnectionMethod::Process
:
69 case Common::ConnectionMethod::Invalid
:
75 m_cacheDir
= Common::writablePath(Common::LOCATION_CACHE
) + m_accountName
+ QLatin1Char('/');;
78 void ImapAccess::alertReceived(const QString
&message
)
80 qDebug() << "alertReceived" << message
;
83 void ImapAccess::connectionError(const QString
&message
)
85 qDebug() << "connectionError" << message
;
88 void ImapAccess::slotLogged(uint parserId
, const Common::LogMessage
&message
)
90 if (message
.kind
!= Common::LOG_IO_READ
) {
91 qDebug() << "LOG" << parserId
<< message
.timestamp
<< message
.kind
<< message
.source
<< message
.message
;
95 QString
ImapAccess::server() const
100 void ImapAccess::setServer(const QString
&server
)
103 m_settings
->setValue(Common::SettingsNames::imapHostKey
, m_server
);
104 emit
serverChanged();
107 QString
ImapAccess::username() const
112 void ImapAccess::setUsername(const QString
&username
)
114 m_username
= username
;
115 m_settings
->setValue(Common::SettingsNames::imapUserKey
, m_username
);
116 emit
usernameChanged();;
119 QString
ImapAccess::password() const
124 void ImapAccess::setPassword(const QString
&password
)
126 m_password
= password
;
129 int ImapAccess::port() const
134 void ImapAccess::setPort(const int port
)
137 m_settings
->setValue(Common::SettingsNames::imapPortKey
, m_port
);
141 QString
ImapAccess::sslMode() const
143 switch (m_connectionMethod
) {
144 case Common::ConnectionMethod::NetCleartext
:
145 return QLatin1String("No");
146 case Common::ConnectionMethod::NetStartTls
:
147 return QLatin1String("StartTLS");
148 case Common::ConnectionMethod::NetDedicatedTls
:
149 return QLatin1String("SSL");
150 case Common::ConnectionMethod::Invalid
:
151 case Common::ConnectionMethod::Process
:
159 void ImapAccess::setSslMode(const QString
&sslMode
)
161 if (sslMode
== QLatin1String("No")) {
162 setConnectionMethod(Common::ConnectionMethod::NetCleartext
);
163 } else if (sslMode
== QLatin1String("SSL")) {
164 setConnectionMethod(Common::ConnectionMethod::NetDedicatedTls
);
165 } else if (sslMode
== QLatin1String("StartTLS")) {
166 setConnectionMethod(Common::ConnectionMethod::NetStartTls
);
172 Common::ConnectionMethod
ImapAccess::connectionMethod() const
174 return m_connectionMethod
;
177 void ImapAccess::setConnectionMethod(const Common::ConnectionMethod mode
)
179 m_connectionMethod
= mode
;
180 switch (m_connectionMethod
) {
181 case Common::ConnectionMethod::Invalid
:
183 case Common::ConnectionMethod::NetCleartext
:
184 case Common::ConnectionMethod::NetStartTls
:
185 m_settings
->setValue(Common::SettingsNames::imapMethodKey
, Common::SettingsNames::methodTCP
);
186 m_settings
->setValue(Common::SettingsNames::imapStartTlsKey
, m_connectionMethod
== Common::ConnectionMethod::NetStartTls
);
188 case Common::ConnectionMethod::NetDedicatedTls
:
189 m_settings
->setValue(Common::SettingsNames::imapMethodKey
, Common::SettingsNames::methodSSL
);
190 // Trying to communicate the fact that this is going to be an encrypted connection, even though
191 // that settings bit is not actually used
192 m_settings
->setValue(Common::SettingsNames::imapStartTlsKey
, true);
194 case Common::ConnectionMethod::Process
:
195 m_settings
->setValue(Common::SettingsNames::imapMethodKey
, Common::SettingsNames::methodProcess
);
198 emit
connMethodChanged();
201 void ImapAccess::doConnect()
203 Q_ASSERT(!m_imapModel
);
205 Imap::Mailbox::SocketFactoryPtr factory
;
206 Imap::Mailbox::TaskFactoryPtr
taskFactory(new Imap::Mailbox::TaskFactory());
208 switch (m_connectionMethod
) {
209 case Common::ConnectionMethod::Invalid
:
210 factory
.reset(new Streams::FakeSocketFactory(Imap::CONN_STATE_LOGOUT
));
212 case Common::ConnectionMethod::NetCleartext
:
213 case Common::ConnectionMethod::NetStartTls
:
214 factory
.reset(new Streams::TlsAbleSocketFactory(server(), port()));
215 factory
->setStartTlsRequired(m_connectionMethod
== Common::ConnectionMethod::NetStartTls
);
217 case Common::ConnectionMethod::NetDedicatedTls
:
218 factory
.reset(new Streams::SslSocketFactory(server(), port()));
220 case Common::ConnectionMethod::Process
:
221 QStringList args
= m_settings
->value(Common::SettingsNames::imapProcessKey
).toString().split(QLatin1Char(' '));
222 if (args
.isEmpty()) {
223 // it's going to fail anyway
224 args
<< QLatin1String("");
226 QString appName
= args
.takeFirst();
227 factory
.reset(new Streams::ProcessSocketFactory(appName
, args
));
231 bool shouldUsePersistentCache
=
232 m_settings
->value(Common::SettingsNames::cacheOfflineKey
).toString() != Common::SettingsNames::cacheOfflineNone
;
234 if (shouldUsePersistentCache
&& !QDir().mkpath(m_cacheDir
)) {
235 onCacheError(tr("Failed to create directory %1").arg(m_cacheDir
));
236 shouldUsePersistentCache
= false;
239 if (shouldUsePersistentCache
) {
240 QFile::Permissions expectedPerms
= QFile::ReadOwner
| QFile::WriteOwner
| QFile::ExeOwner
;
241 if (QFileInfo(m_cacheDir
).permissions() != expectedPerms
) {
242 if (!QFile::setPermissions(m_cacheDir
, expectedPerms
)) {
244 onCacheError(tr("Failed to set safe permissions on cache directory %1").arg(m_cacheDir
));
245 shouldUsePersistentCache
= false;
251 Imap::Mailbox::AbstractCache
*cache
= 0;
253 if (!shouldUsePersistentCache
) {
254 cache
= new Imap::Mailbox::MemoryCache(this);
256 cache
= new Imap::Mailbox::CombinedCache(this, QLatin1String("trojita-imap-cache"), m_cacheDir
);
257 connect(cache
, SIGNAL(error(QString
)), this, SLOT(onCacheError(QString
)));
258 if (! static_cast<Imap::Mailbox::CombinedCache
*>(cache
)->open()) {
259 // Error message was already shown by the cacheError() slot
260 cache
->deleteLater();
261 cache
= new Imap::Mailbox::MemoryCache(this);
263 if (m_settings
->value(Common::SettingsNames::cacheOfflineKey
).toString() == Common::SettingsNames::cacheOfflineAll
) {
264 cache
->setRenewalThreshold(0);
266 const int defaultCacheLifetime
= 30;
268 int num
= m_settings
->value(Common::SettingsNames::cacheOfflineNumberDaysKey
, defaultCacheLifetime
).toInt(&ok
);
270 num
= defaultCacheLifetime
;
271 cache
->setRenewalThreshold(num
);
276 m_imapModel
= new Imap::Mailbox::Model(this, cache
, std::move(factory
), std::move(taskFactory
));
277 m_imapModel
->setObjectName(QString::fromUtf8("imapModel-%1").arg(m_accountName
));
278 m_imapModel
->setCapabilitiesBlacklist(m_settings
->value(Common::SettingsNames::imapBlacklistedCapabilities
).toStringList());
279 m_imapModel
->setProperty("trojita-imap-enable-id", m_settings
->value(Common::SettingsNames::imapEnableId
, true).toBool());
280 connect(m_imapModel
, SIGNAL(alertReceived(QString
)), this, SLOT(alertReceived(QString
)));
281 connect(m_imapModel
, SIGNAL(connectionError(QString
)), this, SLOT(connectionError(QString
)));
282 //connect(m_imapModel, SIGNAL(logged(uint,Common::LogMessage)), this, SLOT(slotLogged(uint,Common::LogMessage)));
283 connect(m_imapModel
, SIGNAL(needsSslDecision(QList
<QSslCertificate
>,QList
<QSslError
>)),
284 this, SLOT(slotSslErrors(QList
<QSslCertificate
>,QList
<QSslError
>)));
286 m_netWatcher
= new Imap::Mailbox::NetworkWatcher(this, m_imapModel
);
287 QMetaObject::invokeMethod(m_netWatcher
,
288 m_settings
->value(Common::SettingsNames::imapStartOffline
).toBool() ?
289 "setNetworkOffline" : "setNetworkOnline",
290 Qt::QueuedConnection
);
292 m_imapModel
->setImapUser(username());
293 if (!m_password
.isNull()) {
294 // Really; the idea is to wait before it has been set for the first time
295 m_imapModel
->setImapPassword(password());
298 m_mailboxModel
= new Imap::Mailbox::MailboxModel(this, m_imapModel
);
299 m_mailboxModel
->setObjectName(QString::fromUtf8("mailboxModel-%1").arg(m_accountName
));
300 m_mailboxSubtreeModel
= new Imap::Mailbox::SubtreeModelOfMailboxModel(this);
301 m_mailboxSubtreeModel
->setObjectName(QString::fromUtf8("mailboxSubtreeModel-%1").arg(m_accountName
));
302 m_mailboxSubtreeModel
->setSourceModel(m_mailboxModel
);
303 m_mailboxSubtreeModel
->setOriginalRoot();
304 m_msgListModel
= new Imap::Mailbox::MsgListModel(this, m_imapModel
);
305 m_msgListModel
->setObjectName(QString::fromUtf8("msgListModel-%1").arg(m_accountName
));
306 m_visibleTasksModel
= new Imap::Mailbox::VisibleTasksModel(this, m_imapModel
->taskModel());
307 m_visibleTasksModel
->setObjectName(QString::fromUtf8("visibleTasksModel-%1").arg(m_accountName
));
308 m_oneMessageModel
= new Imap::Mailbox::OneMessageModel(m_imapModel
);
309 m_oneMessageModel
->setObjectName(QString::fromUtf8("oneMessageModel-%1").arg(m_accountName
));
310 m_msgQNAM
= new Imap::Network::MsgPartNetAccessManager(this);
311 m_msgQNAM
->setObjectName(QString::fromUtf8("m_msgQNAM-%1").arg(m_accountName
));
312 emit
modelsChanged();
315 void ImapAccess::onCacheError(const QString
&message
)
318 m_imapModel
->setCache(new Imap::Mailbox::MemoryCache(m_imapModel
));
320 emit
cacheError(message
);
323 QObject
*ImapAccess::imapModel() const
328 QObject
*ImapAccess::mailboxModel() const
330 return m_mailboxSubtreeModel
;
333 QObject
*ImapAccess::msgListModel() const
335 return m_msgListModel
;
338 QObject
*ImapAccess::visibleTasksModel() const
340 return m_visibleTasksModel
;
343 QObject
*ImapAccess::oneMessageModel() const
345 return m_oneMessageModel
;
348 QObject
*ImapAccess::networkWatcher() const
353 QNetworkAccessManager
*ImapAccess::msgQNAM() const
358 void ImapAccess::openMessage(const QString
&mailboxName
, const uint uid
)
360 QModelIndex msgIndex
= m_imapModel
->messageIndexByUid(mailboxName
, uid
);
361 m_oneMessageModel
->setMessage(msgIndex
);
362 static_cast<Imap::Network::MsgPartNetAccessManager
*>(m_msgQNAM
)->setModelMessage(msgIndex
);
365 QString
ImapAccess::prettySize(const uint bytes
) const
367 return Imap::Mailbox::PrettySize::prettySize(bytes
, Imap::Mailbox::PrettySize::WITH_BYTES_SUFFIX
);
370 void ImapAccess::slotSslErrors(const QList
<QSslCertificate
> &sslCertificateChain
, const QList
<QSslError
> &sslErrors
)
372 m_sslChain
= sslCertificateChain
;
373 m_sslErrors
= sslErrors
;
375 QByteArray lastKnownPubKey
= m_settings
->value(Common::SettingsNames::imapSslPemPubKey
).toByteArray();
376 if (!m_sslChain
.isEmpty() && !lastKnownPubKey
.isEmpty() && lastKnownPubKey
== m_sslChain
[0].publicKey().toPem()) {
377 // This certificate chain contains the same public keys as the last time; we should accept that
378 m_imapModel
->setSslPolicy(m_sslChain
, m_sslErrors
, true);
380 Imap::Mailbox::CertificateUtils::formatSslState(
381 m_sslChain
, lastKnownPubKey
, m_sslErrors
, &m_sslInfoTitle
, &m_sslInfoMessage
, &m_sslInfoIcon
);
382 emit
checkSslPolicy();
386 void ImapAccess::setSslPolicy(bool accept
)
388 if (accept
&& !m_sslChain
.isEmpty()) {
389 m_settings
->setValue(Common::SettingsNames::imapSslPemPubKey
, m_sslChain
[0].publicKey().toPem());
391 m_imapModel
->setSslPolicy(m_sslChain
, m_sslErrors
, accept
);
394 void ImapAccess::forgetSslCertificate()
396 m_settings
->remove(Common::SettingsNames::imapSslPemPubKey
);
399 QString
ImapAccess::sslInfoTitle() const
401 return m_sslInfoTitle
;
404 QString
ImapAccess::sslInfoMessage() const
406 return m_sslInfoMessage
;
409 Imap::Mailbox::CertificateUtils::IconType
ImapAccess::sslInfoIcon() const
411 return m_sslInfoIcon
;
414 QString
ImapAccess::mailboxListMailboxName() const
416 return m_mailboxSubtreeModel
->rootIndex().data(Imap::Mailbox::RoleMailboxName
).toString();
419 QString
ImapAccess::mailboxListShortMailboxName() const
421 return m_mailboxSubtreeModel
->rootIndex().data(Imap::Mailbox::RoleShortMailboxName
).toString();
424 /** @short Persistently remove the local cache of IMAP data
426 This method should be called by the UI when the user changes its connection details, i.e. when there's a big chance that we are
427 connecting to a completely different server since the last time.
429 void ImapAccess::nukeCache()
431 QFile::remove(m_cacheDir
);