File indexing completed on 2025-01-05 04:58:39
0001 /* 0002 * Copyright (C) 2015 Christian Mollekopf <chrigi_1@fastmail.fm> 0003 * 0004 * This program is free software; you can redistribute it and/or modify 0005 * it under the terms of the GNU General Public License as published by 0006 * the Free Software Foundation; either version 2 of the License, or 0007 * (at your option) any later version. 0008 * 0009 * This program is distributed in the hope that it will be useful, 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 * GNU General Public License for more details. 0013 * 0014 * You should have received a copy of the GNU General Public License 0015 * along with this program; if not, write to the 0016 * Free Software Foundation, Inc., 0017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 0018 */ 0019 0020 #pragma once 0021 #include <QDeadlineTimer> 0022 0023 #include <KAsync/Async> 0024 0025 #include <KMime/KMimeMessage> 0026 #include <KIMAP2/ListJob> 0027 #include <KIMAP2/Session> 0028 #include <KIMAP2/FetchJob> 0029 #include <KIMAP2/SearchJob> 0030 #include <KIMAP2/LoginJob> 0031 0032 namespace Imap { 0033 0034 enum ErrorCode { 0035 NoError, 0036 LoginFailed, 0037 HostNotFoundError, 0038 CouldNotConnectError, 0039 SslHandshakeError, 0040 ConnectionLost, 0041 MissingCredentialsError, 0042 CommandFailed, 0043 UnknownError 0044 }; 0045 0046 namespace Flags 0047 { 0048 /// The flag for a message being seen (i.e. opened by user). 0049 extern const char* Seen; 0050 /// The flag for a message being deleted by the user. 0051 extern const char* Deleted; 0052 /// The flag for a message being replied to by the user. 0053 extern const char* Answered; 0054 /// The flag for a message being marked as flagged. 0055 extern const char* Flagged; 0056 } 0057 0058 namespace FolderFlags 0059 { 0060 extern const char* Noinferiors; 0061 extern const char* Noselect; 0062 extern const char* Marked; 0063 extern const char* Unmarked; 0064 extern const char* Subscribed; 0065 extern const char* Sent; 0066 extern const char* Trash; 0067 extern const char* Archive; 0068 extern const char* Junk; 0069 extern const char* Flagged; 0070 extern const char* All; 0071 extern const char* Drafts; 0072 } 0073 0074 namespace Capabilities 0075 { 0076 extern const char* Condstore; 0077 extern const char* Uidplus; 0078 extern const char* Namespace; 0079 } 0080 0081 struct Message { 0082 qint64 uid; 0083 qint64 size; 0084 KIMAP2::MessageAttributes attributes; 0085 KIMAP2::MessageFlags flags; 0086 KMime::Message::Ptr msg; 0087 bool fullPayload; 0088 }; 0089 0090 bool flagsContain(const QByteArray &f, const QByteArrayList &flags); 0091 0092 struct Folder { 0093 Folder() = default; 0094 Folder(const QString &path, const QString &ns, const QChar &separator, bool noselect_, bool subscribed_, const QByteArrayList &flags_) 0095 : noselect(noselect_), 0096 subscribed(subscribed_), 0097 flags(flags_), 0098 mPath(path), 0099 mNamespace(ns), 0100 mSeparator(separator) 0101 { 0102 } 0103 0104 Folder(const QString &path_) 0105 : mPath(path_) 0106 { 0107 } 0108 0109 QString path() const 0110 { 0111 Q_ASSERT(!mPath.isEmpty()); 0112 return mPath; 0113 } 0114 0115 QString parentPath() const 0116 { 0117 Q_ASSERT(!mSeparator.isNull()); 0118 auto parts = mPath.split(mSeparator); 0119 parts.removeLast(); 0120 auto parentPath = parts.join(mSeparator); 0121 //Don't return the namespace for root folders as parent folder 0122 if (mNamespace.startsWith(parentPath)) { 0123 return QString{}; 0124 } 0125 return parentPath; 0126 } 0127 0128 Folder parentFolder() const 0129 { 0130 Folder parent; 0131 parent.mPath = parentPath(); 0132 parent.mNamespace = mNamespace; 0133 parent.mSeparator = mSeparator; 0134 return parent; 0135 } 0136 0137 QString name() const 0138 { 0139 auto pathParts = mPath.split(mSeparator); 0140 Q_ASSERT(!pathParts.isEmpty()); 0141 return pathParts.last(); 0142 } 0143 0144 bool noselect = false; 0145 bool subscribed = false; 0146 QByteArrayList flags; 0147 0148 private: 0149 QString mPath; 0150 QString mNamespace; 0151 QChar mSeparator; 0152 }; 0153 0154 struct SelectResult { 0155 qint64 uidValidity; 0156 qint64 uidNext; 0157 quint64 highestModSequence; 0158 }; 0159 0160 class Namespaces { 0161 public: 0162 QList<KIMAP2::MailBoxDescriptor> personal; 0163 QList<KIMAP2::MailBoxDescriptor> shared; 0164 QList<KIMAP2::MailBoxDescriptor> user; 0165 0166 KIMAP2::MailBoxDescriptor getDefaultNamespace() 0167 { 0168 return personal.isEmpty() ? KIMAP2::MailBoxDescriptor{} : personal.first(); 0169 } 0170 0171 KIMAP2::MailBoxDescriptor getNamespace(const QString &mailbox) 0172 { 0173 for (const auto &ns : personal) { 0174 if (mailbox.startsWith(ns.name)) { 0175 return ns; 0176 } 0177 } 0178 for (const auto &ns : shared) { 0179 if (mailbox.startsWith(ns.name)) { 0180 return ns; 0181 } 0182 } 0183 for (const auto &ns : user) { 0184 if (mailbox.startsWith(ns.name)) { 0185 return ns; 0186 } 0187 } 0188 return KIMAP2::MailBoxDescriptor{}; 0189 } 0190 }; 0191 0192 using namespace std::chrono_literals; 0193 class CachedSession { 0194 public: 0195 0196 CachedSession() = default; 0197 CachedSession(KIMAP2::Session *session, const QStringList &cap, const Namespaces &ns) : mSession(session), mCapabilities(cap), mNamespaces(ns), mTimer{30s} 0198 { 0199 } 0200 0201 bool operator==(const CachedSession &other) const 0202 { 0203 return mSession && (mSession == other.mSession); 0204 } 0205 0206 bool isConnected() const 0207 { 0208 return (mSession->state() == KIMAP2::Session::State::Authenticated || mSession->state() == KIMAP2::Session::State::Selected) ; 0209 } 0210 0211 bool isExpired() 0212 { 0213 // Don't touch sessions that have been cached for over 5min 0214 // This is useful e.g. after a sleep, so we don't use a stale session, 0215 // that will then fail anyways. 0216 return mTimer.hasExpired(); 0217 } 0218 0219 bool isValid() 0220 { 0221 return mSession; 0222 } 0223 0224 KIMAP2::Session *mSession = nullptr; 0225 QStringList mCapabilities; 0226 Namespaces mNamespaces; 0227 0228 private: 0229 QDeadlineTimer mTimer; 0230 }; 0231 0232 class SessionCache : public QObject { 0233 Q_OBJECT 0234 public: 0235 void recycleSession(const CachedSession &session) 0236 { 0237 if (!session.isConnected()) { 0238 return; 0239 } 0240 QObject::connect(session.mSession, &KIMAP2::Session::stateChanged, this, [this, session](KIMAP2::Session::State newState, KIMAP2::Session::State oldState) { 0241 if (newState == KIMAP2::Session::Disconnected) { 0242 mSessions.removeOne(session); 0243 } 0244 }); 0245 mSessions << session; 0246 } 0247 0248 CachedSession getSession() 0249 { 0250 while (!mSessions.isEmpty()) { 0251 auto session = mSessions.takeLast(); 0252 if (session.isConnected() && !session.isExpired()) { 0253 return session; 0254 } 0255 } 0256 return {}; 0257 } 0258 0259 bool isEmpty() const 0260 { 0261 return mSessions.isEmpty(); 0262 } 0263 0264 int size() const 0265 { 0266 return mSessions.size(); 0267 } 0268 private: 0269 QList<CachedSession> mSessions; 0270 }; 0271 0272 enum EncryptionMode { 0273 NoEncryption, 0274 Tls, 0275 Starttls 0276 }; 0277 0278 using AuthenticationMode = KIMAP2::LoginJob::AuthenticationMode; 0279 0280 AuthenticationMode fromAuthString(const QString &s); 0281 0282 class ImapServerProxy { 0283 public: 0284 ImapServerProxy(const QString &serverUrl, int port, EncryptionMode encryption, AuthenticationMode authentication = AuthenticationMode::Plain, SessionCache *sessionCache = nullptr); 0285 0286 //Standard IMAP calls 0287 KAsync::Job<void> login(const QString &username, const QString &password); 0288 KAsync::Job<void> logout(); 0289 KAsync::Job<SelectResult> select(const QString &mailbox); 0290 KAsync::Job<SelectResult> select(const Folder &mailbox); 0291 KAsync::Job<SelectResult> examine(const QString &mailbox); 0292 KAsync::Job<SelectResult> examine(const Folder &mailbox); 0293 KAsync::Job<qint64> append(const QString &mailbox, const QByteArray &content, const QList<QByteArray> &flags = QList<QByteArray>(), const QDateTime &internalDate = QDateTime()); 0294 KAsync::Job<void> store(const KIMAP2::ImapSet &set, const QList<QByteArray> &flags); 0295 KAsync::Job<void> storeFlags(const KIMAP2::ImapSet &set, const QList<QByteArray> &flags); 0296 KAsync::Job<void> addFlags(const KIMAP2::ImapSet &set, const QList<QByteArray> &flags); 0297 KAsync::Job<void> removeFlags(const KIMAP2::ImapSet &set, const QList<QByteArray> &flags); 0298 KAsync::Job<void> create(const QString &mailbox); 0299 KAsync::Job<void> rename(const QString &mailbox, const QString &newMailbox); 0300 KAsync::Job<void> remove(const QString &mailbox); 0301 KAsync::Job<void> subscribe(const QString &mailbox); 0302 KAsync::Job<void> expunge(); 0303 KAsync::Job<void> expunge(const KIMAP2::ImapSet &set); 0304 KAsync::Job<void> copy(const KIMAP2::ImapSet &set, const QString &newMailbox); 0305 KAsync::Job<QVector<qint64>> search(const KIMAP2::ImapSet &set); 0306 KAsync::Job<QVector<qint64>> search(const KIMAP2::Term &term); 0307 0308 typedef std::function<void(const KIMAP2::FetchJob::Result &)> FetchCallback; 0309 0310 KAsync::Job<void> fetch(const KIMAP2::ImapSet &set, KIMAP2::FetchJob::FetchScope scope, FetchCallback callback); 0311 KAsync::Job<void> fetch(const KIMAP2::ImapSet &set, KIMAP2::FetchJob::FetchScope scope, const std::function<void(const Message &)> &callback); 0312 KAsync::Job<void> list(KIMAP2::ListJob::Option option, const std::function<void(const KIMAP2::MailBoxDescriptor &mailboxes,const QList<QByteArray> &flags)> &callback); 0313 0314 QStringList getCapabilities() const; 0315 0316 //Composed calls that do login etc. 0317 KAsync::Job<QVector<qint64>> fetchHeaders(const QString &mailbox, qint64 minUid = 1); 0318 KAsync::Job<void> remove(const QString &mailbox, const KIMAP2::ImapSet &set); 0319 KAsync::Job<void> remove(const QString &mailbox, const QByteArray &imapSet); 0320 KAsync::Job<void> move(const QString &mailbox, const KIMAP2::ImapSet &set, const QString &newMailbox); 0321 KAsync::Job<QString> createSubfolder(const QString &parentMailbox, const QString &folderName); 0322 KAsync::Job<QString> renameSubfolder(const QString &mailbox, const QString &newName); 0323 KAsync::Job<QVector<qint64>> fetchUids(); 0324 KAsync::Job<QVector<qint64>> fetchUidsSince(const QDate &since); 0325 KAsync::Job<QVector<qint64>> fetchUidsSince(const QDate &since, qint64 lowerBound); 0326 0327 QString mailboxFromFolder(const Folder &) const; 0328 0329 KAsync::Job<void> fetchFolders(std::function<void(const Folder &)> callback); 0330 KAsync::Job<void> fetchMessages(const Folder &folder, std::function<void(const Message &)> callback, std::function<void(int, int)> progress = std::function<void(int, int)>()); 0331 KAsync::Job<void> fetchMessages(const Folder &folder, qint64 uidNext, std::function<void(const Message &)> callback, std::function<void(int, int)> progress = std::function<void(int, int)>()); 0332 KAsync::Job<void> fetchMessages(const Folder &folder, const QVector<qint64> &uidsToFetch, bool headersOnly, std::function<void(const Message &)> callback, std::function<void(int, int)> progress); 0333 KAsync::Job<void> fetchFlags(const KIMAP2::ImapSet &set, qint64 changedsince, std::function<void(const Message &)> callback); 0334 KAsync::Job<QVector<qint64>> fetchUids(const Folder &folder); 0335 0336 private: 0337 KAsync::Job<void> getMetaData(std::function<void(const QHash<QString, QMap<QByteArray, QByteArray> > &metadata)> callback); 0338 bool isGmail() const; 0339 0340 QString getNamespace(const QString &name); 0341 QObject mGuard; 0342 SessionCache *mSessionCache; 0343 KIMAP2::Session *mSession; 0344 QStringList mCapabilities; 0345 Namespaces mNamespaces; 0346 EncryptionMode mEncryptionMode; 0347 AuthenticationMode mAuthenticationMode; 0348 const QString mServerUrl; 0349 const int mPort; 0350 }; 0351 0352 }