/* * Copyright (C) 2015 Christian Mollekopf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #include #include #include #include #include namespace Imap { enum ErrorCode { NoError, LoginFailed, HostNotFoundError, CouldNotConnectError, SslHandshakeError, ConnectionLost, MissingCredentialsError, UnknownError }; namespace Flags { /// The flag for a message being seen (i.e. opened by user). extern const char* Seen; /// The flag for a message being deleted by the user. extern const char* Deleted; /// The flag for a message being replied to by the user. extern const char* Answered; /// The flag for a message being marked as flagged. extern const char* Flagged; } namespace FolderFlags { extern const char* Noinferiors; extern const char* Noselect; extern const char* Marked; extern const char* Unmarked; extern const char* Subscribed; extern const char* Sent; extern const char* Trash; extern const char* Archive; extern const char* Junk; extern const char* Flagged; extern const char* All; extern const char* Drafts; } namespace Capabilities { extern const char* Condstore; extern const char* Uidplus; extern const char* Namespace; } struct Message { qint64 uid; qint64 size; KIMAP2::MessageAttributes attributes; KIMAP2::MessageFlags flags; KMime::Message::Ptr msg; bool fullPayload; }; bool flagsContain(const QByteArray &f, const QByteArrayList &flags); struct Folder { Folder() = default; Folder(const QString &path, const QString &ns, const QChar &separator, bool noselect_, bool subscribed_, const QByteArrayList &flags_) : noselect(noselect_), subscribed(subscribed_), flags(flags_), mPath(path), mNamespace(ns), mSeparator(separator) { } Folder(const QString &path_) : mPath(path_) { } QString path() const { Q_ASSERT(!mPath.isEmpty()); return mPath; } QString parentPath() const { Q_ASSERT(!mSeparator.isNull()); auto parts = mPath.split(mSeparator); parts.removeLast(); auto parentPath = parts.join(mSeparator); //Don't return the namespace for root folders as parent folder if (mNamespace.startsWith(parentPath)) { return QString{}; } return parentPath; } Folder parentFolder() const { Folder parent; parent.mPath = parentPath(); parent.mNamespace = mNamespace; parent.mSeparator = mSeparator; return parent; } QString name() const { auto pathParts = mPath.split(mSeparator); Q_ASSERT(!pathParts.isEmpty()); return pathParts.last(); } bool noselect = false; bool subscribed = false; QByteArrayList flags; private: QString mPath; QString mNamespace; QChar mSeparator; }; struct SelectResult { qint64 uidValidity; qint64 uidNext; quint64 highestModSequence; }; class Namespaces { public: QList personal; QList shared; QList user; KIMAP2::MailBoxDescriptor getDefaultNamespace() { return personal.isEmpty() ? KIMAP2::MailBoxDescriptor{} : personal.first(); } KIMAP2::MailBoxDescriptor getNamespace(const QString &mailbox) { for (const auto &ns : personal) { if (mailbox.startsWith(ns.name)) { return ns; } } for (const auto &ns : shared) { if (mailbox.startsWith(ns.name)) { return ns; } } for (const auto &ns : user) { if (mailbox.startsWith(ns.name)) { return ns; } } return KIMAP2::MailBoxDescriptor{}; } }; class CachedSession { public: CachedSession() = default; CachedSession(KIMAP2::Session *session, const QStringList &cap, const Namespaces &ns) : mSession(session), mCapabilities(cap), mNamespaces(ns) { } bool operator==(const CachedSession &other) const { return mSession && (mSession == other.mSession); } bool isConnected() { return (mSession->state() == KIMAP2::Session::State::Authenticated || mSession->state() == KIMAP2::Session::State::Selected) ; } bool isValid() { return mSession; } KIMAP2::Session *mSession = nullptr; QStringList mCapabilities; Namespaces mNamespaces; }; class SessionCache : public QObject { Q_OBJECT public: void recycleSession(const CachedSession &session) { QObject::connect(session.mSession, &KIMAP2::Session::stateChanged, this, [this, session](KIMAP2::Session::State newState, KIMAP2::Session::State oldState) { if (newState == KIMAP2::Session::Disconnected) { mSessions.removeOne(session); } }); mSessions << session; } CachedSession getSession() { while (!mSessions.isEmpty()) { auto session = mSessions.takeLast(); if (session.isConnected()) { return session; } } return {}; } bool isEmpty() const { return mSessions.isEmpty(); } private: QList mSessions; }; class ImapServerProxy { public: ImapServerProxy(const QString &serverUrl, int port, SessionCache *sessionCache = nullptr); //Standard IMAP calls KAsync::Job login(const QString &username, const QString &password); KAsync::Job logout(); KAsync::Job select(const QString &mailbox); KAsync::Job append(const QString &mailbox, const QByteArray &content, const QList &flags = QList(), const QDateTime &internalDate = QDateTime()); KAsync::Job store(const KIMAP2::ImapSet &set, const QList &flags); KAsync::Job storeFlags(const KIMAP2::ImapSet &set, const QList &flags); KAsync::Job addFlags(const KIMAP2::ImapSet &set, const QList &flags); KAsync::Job removeFlags(const KIMAP2::ImapSet &set, const QList &flags); KAsync::Job create(const QString &mailbox); KAsync::Job rename(const QString &mailbox, const QString &newMailbox); KAsync::Job remove(const QString &mailbox); KAsync::Job expunge(); KAsync::Job expunge(const KIMAP2::ImapSet &set); KAsync::Job copy(const KIMAP2::ImapSet &set, const QString &newMailbox); KAsync::Job> search(const KIMAP2::ImapSet &set); KAsync::Job> search(const KIMAP2::Term &term); typedef std::function FetchCallback; KAsync::Job fetch(const KIMAP2::ImapSet &set, KIMAP2::FetchJob::FetchScope scope, FetchCallback callback); KAsync::Job fetch(const KIMAP2::ImapSet &set, KIMAP2::FetchJob::FetchScope scope, const std::function &callback); KAsync::Job list(KIMAP2::ListJob::Option option, const std::function &flags)> &callback); QStringList getCapabilities() const; //Composed calls that do login etc. KAsync::Job> fetchHeaders(const QString &mailbox, qint64 minUid = 1); KAsync::Job remove(const QString &mailbox, const KIMAP2::ImapSet &set); KAsync::Job remove(const QString &mailbox, const QByteArray &imapSet); KAsync::Job move(const QString &mailbox, const KIMAP2::ImapSet &set, const QString &newMailbox); KAsync::Job createSubfolder(const QString &parentMailbox, const QString &folderName); KAsync::Job renameSubfolder(const QString &mailbox, const QString &newName); KAsync::Job> fetchUids(const QString &mailbox); KAsync::Job> fetchUidsSince(const QString &mailbox, const QDate &since); QString mailboxFromFolder(const Folder &) const; KAsync::Job fetchFolders(std::function callback); KAsync::Job fetchMessages(const Folder &folder, std::function callback, std::function progress = std::function()); KAsync::Job fetchMessages(const Folder &folder, qint64 uidNext, std::function callback, std::function progress = std::function()); KAsync::Job fetchMessages(const Folder &folder, const QVector &uidsToFetch, bool headersOnly, std::function callback, std::function progress); KAsync::Job fetchFlags(const Folder &folder, const KIMAP2::ImapSet &set, qint64 changedsince, std::function callback); KAsync::Job> fetchUids(const Folder &folder); private: KAsync::Job getMetaData(std::function > &metadata)> callback); bool isGmail() const; QString getNamespace(const QString &name); QObject mGuard; SessionCache *mSessionCache; KIMAP2::Session *mSession; QStringList mCapabilities; Namespaces mNamespaces; }; }