File indexing completed on 2025-01-05 04:58:38

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 #include "imapserverproxy.h"
0020 
0021 #include <KIMAP2/LoginJob>
0022 #include <KIMAP2/LogoutJob>
0023 #include <KIMAP2/NamespaceJob>
0024 #include <KIMAP2/SelectJob>
0025 #include <KIMAP2/AppendJob>
0026 #include <KIMAP2/CreateJob>
0027 #include <KIMAP2/CopyJob>
0028 #include <KIMAP2/RenameJob>
0029 #include <KIMAP2/DeleteJob>
0030 #include <KIMAP2/StoreJob>
0031 #include <KIMAP2/ExpungeJob>
0032 #include <KIMAP2/CapabilitiesJob>
0033 #include <KIMAP2/SearchJob>
0034 #include <KIMAP2/GetMetaDataJob>
0035 #include <KIMAP2/SubscribeJob>
0036 
0037 #include <KCoreAddons/KJob>
0038 #include <QHostInfo>
0039 
0040 #include "log.h"
0041 #include "test.h"
0042 
0043 using namespace Imap;
0044 
0045 const char* Imap::Flags::Seen = "\\Seen";
0046 const char* Imap::Flags::Deleted = "\\Deleted";
0047 const char* Imap::Flags::Answered = "\\Answered";
0048 const char* Imap::Flags::Flagged = "\\Flagged";
0049 
0050 const char* Imap::FolderFlags::Noselect = "\\Noselect";
0051 const char* Imap::FolderFlags::Noinferiors = "\\Noinferiors";
0052 const char* Imap::FolderFlags::Marked = "\\Marked";
0053 const char* Imap::FolderFlags::Unmarked = "\\Unmarked";
0054 const char* Imap::FolderFlags::Subscribed = "\\Subscribed";
0055 //Special use
0056 const char* Imap::FolderFlags::Sent = "\\Sent";
0057 const char* Imap::FolderFlags::Trash = "\\Trash";
0058 const char* Imap::FolderFlags::Archive = "\\Archive";
0059 const char* Imap::FolderFlags::Junk = "\\Junk";
0060 const char* Imap::FolderFlags::Flagged = "\\Flagged";
0061 const char* Imap::FolderFlags::Drafts = "\\Drafts";
0062 
0063 const char* Imap::Capabilities::Namespace = "NAMESPACE";
0064 const char* Imap::Capabilities::Uidplus = "UIDPLUS";
0065 const char* Imap::Capabilities::Condstore = "CONDSTORE";
0066 
0067 static int translateImapError(KJob *job)
0068 {
0069     switch (job->error()) {
0070         case KIMAP2::HostNotFound:
0071             return Imap::HostNotFoundError;
0072         case KIMAP2::CouldNotConnect:
0073             return Imap::CouldNotConnectError;
0074         case KIMAP2::SslHandshakeFailed:
0075             return Imap::SslHandshakeError;
0076         case KIMAP2::ConnectionLost:
0077             return Imap::ConnectionLost;
0078         case KIMAP2::LoginFailed:
0079             return Imap::LoginFailed;
0080         case KIMAP2::CommandFailed:
0081             return Imap::CommandFailed;
0082     }
0083     return Imap::UnknownError;
0084 }
0085 
0086 template <typename T>
0087 static KAsync::Job<T> runJob(KJob *job, const std::function<T(KJob*)> &f)
0088 {
0089     return KAsync::start<T>([job, f](KAsync::Future<T> &future) {
0090         QObject::connect(job, &KJob::result, [&future, f](KJob *job) {
0091             SinkTrace() << "Job done: " << job->metaObject()->className();
0092             if (job->error()) {
0093                 SinkWarning() << "Job failed: " << job->errorString() << job->metaObject()->className() << job->error();
0094                 auto proxyError = translateImapError(job);
0095                 future.setError(proxyError, job->errorString());
0096             } else {
0097                 future.setValue(f(job));
0098                 future.setFinished();
0099             }
0100         });
0101         SinkTrace() << "Starting job: " << job->metaObject()->className();
0102         job->start();
0103     });
0104 }
0105 
0106 static KAsync::Job<void> runJob(KJob *job)
0107 {
0108     return KAsync::start<void>([job](KAsync::Future<void> &future) {
0109         QObject::connect(job, &KJob::result, [&future](KJob *job) {
0110             SinkTrace() << "Job done: " << job->metaObject()->className();
0111             if (job->error()) {
0112                 SinkWarning() << "Job failed: " << job->errorString() << job->metaObject()->className() << job->error();
0113                 auto proxyError = translateImapError(job);
0114                 future.setError(proxyError, job->errorString());
0115             } else {
0116                 future.setFinished();
0117             }
0118         });
0119         SinkTrace() << "Starting job: " << job->metaObject()->className();
0120         job->start();
0121     });
0122 }
0123 
0124 static int socketTimeout()
0125 {
0126     if (Sink::Test::testModeEnabled()) {
0127         return 5;
0128     }
0129     return 40;
0130 }
0131 
0132 KIMAP2::Session *createNewSession(const QString &serverUrl, int port)
0133 {
0134     auto newSession = new KIMAP2::Session(serverUrl, qint16(port));
0135     newSession->setTimeout(socketTimeout());
0136     QObject::connect(newSession, &KIMAP2::Session::sslErrors, [=](const QList<QSslError> &errors) {
0137         SinkWarning() << "Received SSL errors:";
0138         for (const auto &e : errors) {
0139             SinkWarning() << "  " << e.error() << ":" << e.errorString() << "Certificate: " << e.certificate().toText();
0140         }
0141         newSession->ignoreErrors(errors);
0142     });
0143     return newSession;
0144 }
0145 
0146 ImapServerProxy::ImapServerProxy(const QString &serverUrl, int port, EncryptionMode encryptionMode, AuthenticationMode authenticationMode, SessionCache *sessionCache) : mSessionCache(sessionCache), mSession(nullptr), mEncryptionMode(encryptionMode), mAuthenticationMode(authenticationMode), mServerUrl(serverUrl), mPort(port)
0147 {
0148 }
0149 
0150 QDebug operator<<(QDebug debug, const KIMAP2::MailBoxDescriptor &c)
0151 {
0152     QDebugStateSaver saver(debug);
0153     debug.nospace() << c.name;
0154     return debug;
0155 }
0156 
0157 KAsync::Job<void> ImapServerProxy::login(const QString &username, const QString &password)
0158 {
0159     if (password.isEmpty()) {
0160         return KAsync::error(Imap::MissingCredentialsError);
0161     }
0162     if (mSessionCache) {
0163         auto session = mSessionCache->getSession();
0164         if (session.isValid()) {
0165             SinkLog() << "Got existing session from session cache.";
0166             mSession = session.mSession;
0167             mCapabilities = session.mCapabilities;
0168             mNamespaces = session.mNamespaces;
0169         }
0170     }
0171     if (!mSession) {
0172         mSession = createNewSession(mServerUrl, mPort);
0173     }
0174     Q_ASSERT(mSession);
0175     if (mSession->state() == KIMAP2::Session::Authenticated || mSession->state() == KIMAP2::Session::Selected) {
0176         //If we blindly reuse the socket it may very well be stale and then we have to wait for it to time out.
0177         //A hostlookup should be fast (a couple of milliseconds once cached), and can typcially tell us quickly
0178         //if the host is no longer available.
0179         auto info = QHostInfo::fromName(mSession->hostName());
0180         if (info.error()) {
0181             SinkLog() << "Failed host lookup, closing the socket" << info.errorString();
0182             mSession->close();
0183             mSession = nullptr;
0184             return KAsync::error(Imap::HostNotFoundError);
0185         } else {
0186             //Prevent the socket from timing out right away, right here (otherwise it just might time out right before we were able to start the job)
0187             mSession->setTimeout(socketTimeout());
0188             SinkLog() << "Reusing existing session.";
0189             return KAsync::null();
0190         }
0191     }
0192     auto loginJob = new KIMAP2::LoginJob(mSession);
0193     loginJob->setUserName(username);
0194     loginJob->setPassword(password);
0195     if (mEncryptionMode == Starttls) {
0196         loginJob->setEncryptionMode(QSsl::TlsV1_0OrLater, true);
0197     } else if (mEncryptionMode == Tls) {
0198         loginJob->setEncryptionMode(QSsl::AnyProtocol, false);
0199     }
0200     loginJob->setAuthenticationMode(mAuthenticationMode);
0201 
0202     auto capabilitiesJob = new KIMAP2::CapabilitiesJob(mSession);
0203     QObject::connect(capabilitiesJob, &KIMAP2::CapabilitiesJob::capabilitiesReceived, &mGuard, [this](const QStringList &capabilities) {
0204         mCapabilities = capabilities;
0205     });
0206     auto namespaceJob = new KIMAP2::NamespaceJob(mSession);
0207 
0208     return runJob(loginJob).then(runJob(capabilitiesJob)).then([this](){
0209         SinkTrace() << "Supported capabilities: " << mCapabilities;
0210         QStringList requiredExtensions = QStringList() << Capabilities::Uidplus << Capabilities::Namespace;
0211         for (const auto &requiredExtension : requiredExtensions) {
0212             if (!mCapabilities.contains(requiredExtension)) {
0213                 SinkWarning() << "Server doesn't support required capability: " << requiredExtension;
0214                 //TODO fail the job
0215             }
0216         }
0217     }).then(runJob(namespaceJob)).then([this, namespaceJob] {
0218         mNamespaces.personal = namespaceJob->personalNamespaces();
0219         mNamespaces.shared = namespaceJob->sharedNamespaces();
0220         mNamespaces.user = namespaceJob->userNamespaces();
0221         // SinkTrace() << "Found personal namespaces: " << mNamespaces.personal;
0222         // SinkTrace() << "Found shared namespaces: " << mNamespaces.shared;
0223         // SinkTrace() << "Found user namespaces: " << mNamespaces.user;
0224     });
0225 }
0226 
0227 KAsync::Job<void> ImapServerProxy::logout()
0228 {
0229     if (mSessionCache) {
0230         SinkLog() << "Recycling session.";
0231         mSessionCache->recycleSession({mSession, mCapabilities, mNamespaces});
0232         return KAsync::null();
0233     }
0234     if (mSession->state() == KIMAP2::Session::State::Authenticated || mSession->state() == KIMAP2::Session::State::Selected) {
0235         return runJob(new KIMAP2::LogoutJob(mSession));
0236     } else {
0237         return KAsync::null();
0238     }
0239 }
0240 
0241 bool ImapServerProxy::isGmail() const
0242 {
0243     //Magic capability that only gmail has
0244     return mCapabilities.contains("X-GM-EXT-1");
0245 }
0246 
0247 KAsync::Job<SelectResult> ImapServerProxy::select(const QString &mailbox)
0248 {
0249     auto select = new KIMAP2::SelectJob(mSession);
0250     select->setMailBox(mailbox);
0251     select->setCondstoreEnabled(mCapabilities.contains(Capabilities::Condstore));
0252     return runJob<SelectResult>(select, [select](KJob* job) -> SelectResult {
0253         return {select->uidValidity(), select->nextUid(), select->highestModSequence()};
0254     }).then([=] (const KAsync::Error &error, const SelectResult &result) {
0255         if (error) {
0256             SinkWarning() << "Select failed: " << mailbox;
0257             return KAsync::error<SelectResult>(error);
0258         }
0259         return KAsync::value<SelectResult>(result);
0260     });
0261 }
0262 
0263 KAsync::Job<SelectResult> ImapServerProxy::select(const Folder &folder)
0264 {
0265     return select(mailboxFromFolder(folder));
0266 }
0267 
0268 KAsync::Job<SelectResult> ImapServerProxy::examine(const QString &mailbox)
0269 {
0270     auto select = new KIMAP2::SelectJob(mSession);
0271     select->setOpenReadOnly(true);
0272     select->setMailBox(mailbox);
0273     select->setCondstoreEnabled(mCapabilities.contains(Capabilities::Condstore));
0274     return runJob<SelectResult>(select, [select](KJob* job) -> SelectResult {
0275         return {select->uidValidity(), select->nextUid(), select->highestModSequence()};
0276     }).then([=] (const KAsync::Error &error, const SelectResult &result) {
0277         if (error) {
0278             SinkWarning() << "Examine failed: " << mailbox;
0279             return KAsync::error<SelectResult>(error);
0280         }
0281         return KAsync::value<SelectResult>(result);
0282     });
0283 }
0284 
0285 KAsync::Job<SelectResult> ImapServerProxy::examine(const Folder &folder)
0286 {
0287     return examine(mailboxFromFolder(folder));
0288 }
0289 
0290 KAsync::Job<qint64> ImapServerProxy::append(const QString &mailbox, const QByteArray &content, const QList<QByteArray> &flags, const QDateTime &internalDate)
0291 {
0292     Q_ASSERT(mSession);
0293     auto append = new KIMAP2::AppendJob(mSession);
0294     append->setMailBox(mailbox);
0295     append->setContent(content);
0296     append->setFlags(flags);
0297     append->setInternalDate(internalDate);
0298     return runJob<qint64>(append, [](KJob *job) -> qint64{
0299         return static_cast<KIMAP2::AppendJob*>(job)->uid();
0300     });
0301 }
0302 
0303 KAsync::Job<void> ImapServerProxy::store(const KIMAP2::ImapSet &set, const QList<QByteArray> &flags)
0304 {
0305     return storeFlags(set, flags);
0306 }
0307 
0308 KAsync::Job<void> ImapServerProxy::storeFlags(const KIMAP2::ImapSet &set, const QList<QByteArray> &flags)
0309 {
0310     Q_ASSERT(mSession);
0311     auto store = new KIMAP2::StoreJob(mSession);
0312     store->setUidBased(true);
0313     store->setMode(KIMAP2::StoreJob::SetFlags);
0314     store->setSequenceSet(set);
0315     store->setFlags(flags);
0316     return runJob(store);
0317 }
0318 
0319 KAsync::Job<void> ImapServerProxy::addFlags(const KIMAP2::ImapSet &set, const QList<QByteArray> &flags)
0320 {
0321     Q_ASSERT(mSession);
0322     auto store = new KIMAP2::StoreJob(mSession);
0323     store->setUidBased(true);
0324     store->setMode(KIMAP2::StoreJob::AppendFlags);
0325     store->setSequenceSet(set);
0326     store->setFlags(flags);
0327     return runJob(store);
0328 }
0329 
0330 KAsync::Job<void> ImapServerProxy::removeFlags(const KIMAP2::ImapSet &set, const QList<QByteArray> &flags)
0331 {
0332     Q_ASSERT(mSession);
0333     auto store = new KIMAP2::StoreJob(mSession);
0334     store->setUidBased(true);
0335     store->setMode(KIMAP2::StoreJob::RemoveFlags);
0336     store->setSequenceSet(set);
0337     store->setFlags(flags);
0338     return runJob(store);
0339 }
0340 
0341 KAsync::Job<void> ImapServerProxy::create(const QString &mailbox)
0342 {
0343     Q_ASSERT(mSession);
0344     auto create = new KIMAP2::CreateJob(mSession);
0345     create->setMailBox(mailbox);
0346     return runJob(create);
0347 }
0348 
0349 KAsync::Job<void> ImapServerProxy::subscribe(const QString &mailbox)
0350 {
0351     Q_ASSERT(mSession);
0352     auto job = new KIMAP2::SubscribeJob(mSession);
0353     job->setMailBox(mailbox);
0354     return runJob(job);
0355 }
0356 
0357 KAsync::Job<void> ImapServerProxy::rename(const QString &mailbox, const QString &newMailbox)
0358 {
0359     Q_ASSERT(mSession);
0360     auto rename = new KIMAP2::RenameJob(mSession);
0361     rename->setSourceMailBox(mailbox);
0362     rename->setDestinationMailBox(newMailbox);
0363     return runJob(rename);
0364 }
0365 
0366 KAsync::Job<void> ImapServerProxy::remove(const QString &mailbox)
0367 {
0368     Q_ASSERT(mSession);
0369     auto job = new KIMAP2::DeleteJob(mSession);
0370     job->setMailBox(mailbox);
0371     return runJob(job);
0372 }
0373 
0374 KAsync::Job<void> ImapServerProxy::expunge()
0375 {
0376     Q_ASSERT(mSession);
0377     auto job = new KIMAP2::ExpungeJob(mSession);
0378     return runJob(job);
0379 }
0380 
0381 KAsync::Job<void> ImapServerProxy::expunge(const KIMAP2::ImapSet &set)
0382 {
0383     Q_ASSERT(mSession);
0384     //FIXME implement UID EXPUNGE
0385     auto job = new KIMAP2::ExpungeJob(mSession);
0386     return runJob(job);
0387 }
0388 
0389 KAsync::Job<void> ImapServerProxy::copy(const KIMAP2::ImapSet &set, const QString &newMailbox)
0390 {
0391     Q_ASSERT(mSession);
0392     auto copy = new KIMAP2::CopyJob(mSession);
0393     copy->setSequenceSet(set);
0394     copy->setUidBased(true);
0395     copy->setMailBox(newMailbox);
0396     return runJob(copy);
0397 }
0398 
0399 KAsync::Job<void> ImapServerProxy::fetch(const KIMAP2::ImapSet &set, KIMAP2::FetchJob::FetchScope scope, FetchCallback callback)
0400 {
0401     Q_ASSERT(mSession);
0402     auto fetch = new KIMAP2::FetchJob(mSession);
0403     fetch->setSequenceSet(set);
0404     fetch->setUidBased(true);
0405     fetch->setScope(scope);
0406     fetch->setAvoidParsing(true);
0407     QObject::connect(fetch, &KIMAP2::FetchJob::resultReceived, callback);
0408     return runJob(fetch);
0409 }
0410 
0411 KAsync::Job<QVector<qint64>> ImapServerProxy::search(const KIMAP2::ImapSet &set)
0412 {
0413     return search(KIMAP2::Term(KIMAP2::Term::Uid, set));
0414 }
0415 
0416 KAsync::Job<QVector<qint64>> ImapServerProxy::search(const KIMAP2::Term &term)
0417 {
0418     Q_ASSERT(mSession);
0419     auto search = new KIMAP2::SearchJob(mSession);
0420     search->setTerm(term);
0421     search->setUidBased(true);
0422     return runJob<QVector<qint64>>(search, [](KJob *job) -> QVector<qint64> {
0423         return static_cast<KIMAP2::SearchJob*>(job)->results();
0424     });
0425 }
0426 
0427 KAsync::Job<void> ImapServerProxy::fetch(const KIMAP2::ImapSet &set, KIMAP2::FetchJob::FetchScope scope, const std::function<void(const Message &)> &callback)
0428 {
0429     const bool fullPayload = (scope.mode == KIMAP2::FetchJob::FetchScope::Full);
0430     return fetch(set, scope,
0431                     [callback, fullPayload](const KIMAP2::FetchJob::Result &result) {
0432                         callback(Message{result.uid, result.size, result.attributes, result.flags, result.message, fullPayload});
0433                     });
0434 }
0435 
0436 QStringList ImapServerProxy::getCapabilities() const
0437 {
0438     return mCapabilities;
0439 }
0440 
0441 KAsync::Job<QVector<qint64>> ImapServerProxy::fetchHeaders(const QString &mailbox, const qint64 minUid)
0442 {
0443     auto list = QSharedPointer<QVector<qint64>>::create();
0444     KIMAP2::FetchJob::FetchScope scope;
0445     scope.mode = KIMAP2::FetchJob::FetchScope::Flags;
0446 
0447     //Fetch headers of all messages
0448     return fetch(KIMAP2::ImapSet(minUid, 0), scope,
0449             [list](const KIMAP2::FetchJob::Result &result) {
0450                 // SinkTrace() << "Received " << uids.size() << " headers from " << mailbox;
0451                 // SinkTrace() << uids.size() << sizes.size() << attrs.size() << flags.size() << messages.size();
0452 
0453                 //TODO based on the data available here, figure out which messages to actually fetch
0454                 //(we only fetched headers and structure so far)
0455                 //We could i.e. build chunks to fetch based on the size
0456 
0457                 list->append(result.uid);
0458             })
0459     .then([list](){
0460         return *list;
0461     });
0462 }
0463 
0464 KAsync::Job<QVector<qint64>> ImapServerProxy::fetchUids()
0465 {
0466     auto notDeleted = KIMAP2::Term(KIMAP2::Term::Deleted);
0467     notDeleted.setNegated(true);
0468     return search(notDeleted);
0469 }
0470 
0471 KAsync::Job<QVector<qint64>> ImapServerProxy::fetchUidsSince(const QDate &since, qint64 lowerBound)
0472 {
0473     auto notDeleted = KIMAP2::Term{KIMAP2::Term::Deleted};
0474     notDeleted.setNegated(true);
0475 
0476     return search(
0477             KIMAP2::Term{KIMAP2::Term::Or, {
0478                 KIMAP2::Term{KIMAP2::Term::And, {{KIMAP2::Term::Since, since}, notDeleted}},
0479                 KIMAP2::Term{KIMAP2::Term::And, {{KIMAP2::Term::Uid, KIMAP2::ImapSet{lowerBound, 0}}, notDeleted}}
0480             }}
0481         );
0482 }
0483 
0484 KAsync::Job<QVector<qint64>> ImapServerProxy::fetchUidsSince(const QDate &since)
0485 {
0486     auto notDeleted = KIMAP2::Term{KIMAP2::Term::Deleted};
0487     notDeleted.setNegated(true);
0488 
0489     return search(KIMAP2::Term{KIMAP2::Term::And, {{KIMAP2::Term::Since, since}, notDeleted}});
0490 }
0491 
0492 KAsync::Job<void> ImapServerProxy::list(KIMAP2::ListJob::Option option, const std::function<void(const KIMAP2::MailBoxDescriptor &mailboxes, const QList<QByteArray> &flags)> &callback)
0493 {
0494     Q_ASSERT(mSession);
0495     auto listJob = new KIMAP2::ListJob(mSession);
0496     listJob->setOption(option);
0497     // listJob->setQueriedNamespaces(serverNamespaces());
0498     QObject::connect(listJob, &KIMAP2::ListJob::resultReceived,
0499             listJob, callback);
0500     return runJob(listJob);
0501 }
0502 
0503 KAsync::Job<void> ImapServerProxy::remove(const QString &mailbox, const KIMAP2::ImapSet &set)
0504 {
0505     return select(mailbox).then<void>(store(set, QByteArrayList() << Flags::Deleted)).then<void>(expunge(set));
0506 }
0507 
0508 KAsync::Job<void> ImapServerProxy::remove(const QString &mailbox, const QByteArray &imapSet)
0509 {
0510     const auto set = KIMAP2::ImapSet::fromImapSequenceSet(imapSet);
0511     return remove(mailbox, set);
0512 }
0513 
0514 
0515 KAsync::Job<void> ImapServerProxy::move(const QString &mailbox, const KIMAP2::ImapSet &set, const QString &newMailbox)
0516 {
0517     return select(mailbox).then<void>(copy(set, newMailbox)).then<void>(store(set, QByteArrayList() << Flags::Deleted)).then<void>(expunge(set));
0518 }
0519 
0520 KAsync::Job<QString> ImapServerProxy::createSubfolder(const QString &parentMailbox, const QString &folderName)
0521 {
0522     return KAsync::start<QString>([this, parentMailbox, folderName]() {
0523         QString folder;
0524         if (parentMailbox.isEmpty()) {
0525             auto ns = mNamespaces.getDefaultNamespace();
0526             folder = ns.name + folderName;
0527         } else {
0528             auto ns = mNamespaces.getNamespace(parentMailbox);
0529             folder = parentMailbox + ns.separator + folderName;
0530         }
0531         SinkTrace() << "Creating subfolder: " << folder;
0532         return create(folder)
0533             .then([=]() {
0534                 return folder;
0535             });
0536     });
0537 }
0538 
0539 KAsync::Job<QString> ImapServerProxy::renameSubfolder(const QString &oldMailbox, const QString &newName)
0540 {
0541     return KAsync::start<QString>([this, oldMailbox, newName] {
0542         auto ns = mNamespaces.getNamespace(oldMailbox);
0543         auto parts = oldMailbox.split(ns.separator);
0544         parts.removeLast();
0545         QString folder = parts.join(ns.separator) + ns.separator + newName;
0546         SinkTrace() << "Renaming subfolder: " << oldMailbox << folder;
0547         return rename(oldMailbox, folder)
0548             .then([=]() {
0549                 return folder;
0550             });
0551     });
0552 }
0553 
0554 QString ImapServerProxy::getNamespace(const QString &name)
0555 {
0556     auto ns = mNamespaces.getNamespace(name);
0557     return ns.name;
0558 }
0559 
0560 static bool caseInsensitiveContains(const QByteArray &f, const QByteArrayList &list) {
0561     return list.contains(f) || list.contains(f.toLower());
0562 }
0563 
0564 bool Imap::flagsContain(const QByteArray &f, const QByteArrayList &flags)
0565 {
0566     return caseInsensitiveContains(f, flags);
0567 }
0568 
0569 AuthenticationMode Imap::fromAuthString(const QString &s)
0570 {
0571     if (s == QStringLiteral("CLEARTEXT")) return KIMAP2::LoginJob::ClearText;
0572     if (s == QStringLiteral("LOGIN")) return KIMAP2::LoginJob::Login;
0573     if (s == QStringLiteral("PLAIN")) return KIMAP2::LoginJob::Plain;
0574     if (s == QStringLiteral("CRAM-MD5")) return KIMAP2::LoginJob::CramMD5;
0575     if (s == QStringLiteral("DIGEST-MD5")) return KIMAP2::LoginJob::DigestMD5;
0576     if (s == QStringLiteral("GSSAPI")) return KIMAP2::LoginJob::GSSAPI;
0577     if (s == QStringLiteral("ANONYMOUS")) return KIMAP2::LoginJob::Anonymous;
0578     if (s == QStringLiteral("XOAUTH2")) return KIMAP2::LoginJob::XOAuth2;
0579     return KIMAP2::LoginJob::Plain;
0580 }
0581 
0582 static void reportFolder(const Folder &f, QSharedPointer<QSet<QString>> reportedList, std::function<void(const Folder &)> callback) {
0583     if (!reportedList->contains(f.path())) {
0584         reportedList->insert(f.path());
0585         auto c = f;
0586         c.noselect = true;
0587         callback(c);
0588         if (!f.parentPath().isEmpty()){
0589             reportFolder(f.parentFolder(), reportedList, callback);
0590         }
0591     }
0592 }
0593 
0594 KAsync::Job<void> ImapServerProxy::getMetaData(std::function<void(const QHash<QString, QMap<QByteArray, QByteArray> > &metadata)> callback)
0595 {
0596     if (!mCapabilities.contains("METADATA")) {
0597         return KAsync::null();
0598     }
0599     Q_ASSERT(mSession);
0600     KIMAP2::GetMetaDataJob *meta = new KIMAP2::GetMetaDataJob(mSession);
0601     meta->setMailBox(QLatin1String("*"));
0602     meta->setServerCapability( KIMAP2::MetaDataJobBase::Metadata );
0603     meta->setDepth(KIMAP2::GetMetaDataJob::AllLevels);
0604     meta->addRequestedEntry("/shared/vendor/kolab/folder-type");
0605     meta->addRequestedEntry("/private/vendor/kolab/folder-type");
0606     return runJob(meta).then<void>([callback, meta] () {
0607         callback(meta->allMetaDataForMailboxes());
0608     });
0609 }
0610 
0611 KAsync::Job<void> ImapServerProxy::fetchFolders(std::function<void(const Folder &)> callback)
0612 {
0613     SinkTrace() << "Fetching folders";
0614     auto subscribedList = QSharedPointer<QSet<QString>>::create() ;
0615     auto reportedList = QSharedPointer<QSet<QString>>::create() ;
0616     auto metaData = QSharedPointer<QHash<QString, QMap<QByteArray, QByteArray>>>::create() ;
0617     return getMetaData([=] (const QHash<QString, QMap<QByteArray, QByteArray>> &m) {
0618         *metaData = m;
0619     }).then(list(KIMAP2::ListJob::NoOption, [=](const KIMAP2::MailBoxDescriptor &mailbox, const QList<QByteArray> &){
0620         *subscribedList << mailbox.name;
0621     })).then(list(KIMAP2::ListJob::IncludeUnsubscribed, [=](const KIMAP2::MailBoxDescriptor &mailbox, const QList<QByteArray> &flags) {
0622         bool noselect = caseInsensitiveContains(FolderFlags::Noselect, flags);
0623         bool subscribed = subscribedList->contains(mailbox.name);
0624         if (isGmail()) {
0625             bool inbox = mailbox.name.toLower() == "inbox";
0626             bool sent = caseInsensitiveContains(FolderFlags::Sent, flags);
0627             bool drafts = caseInsensitiveContains(FolderFlags::Drafts, flags);
0628             bool trash = caseInsensitiveContains(FolderFlags::Trash, flags);
0629             /**
0630              * Because gmail duplicates messages all over the place we only support a few selected folders for now that should be mostly exclusive.
0631              */
0632             if (!(inbox || sent || drafts || trash)) {
0633                 return;
0634             }
0635         }
0636         SinkTrace() << "Found mailbox: " << mailbox.name << flags << FolderFlags::Noselect << noselect  << " sub: " << subscribed;
0637         //Ignore all non-mail folders
0638         if (metaData->contains(mailbox.name)) {
0639             auto m = metaData->value(mailbox.name);
0640             auto sharedType = m.value("/shared/vendor/kolab/folder-type");
0641             auto privateType = m.value("/private/vendor/kolab/folder-type");
0642             auto type = !privateType.isEmpty() ? privateType : sharedType;
0643             if (!type.isEmpty() && !type.contains("mail")) {
0644                 SinkTrace() << "Skipping due to folder type: " << type;
0645                 return;
0646             }
0647         }
0648         auto ns = getNamespace(mailbox.name);
0649         auto folder = Folder{mailbox.name, ns, mailbox.separator, noselect, subscribed, flags};
0650 
0651         //call callback for parents if that didn't already happen.
0652         //This is necessary because we can have missing bits in the hierarchy in IMAP, but this will not work in sink because we'd end up with an incomplete tree.
0653         if (!folder.parentPath().isEmpty() && !reportedList->contains(folder.parentPath())) {
0654             reportFolder(folder.parentFolder(), reportedList, callback);
0655         }
0656         reportedList->insert(folder.path());
0657         callback(folder);
0658     }));
0659 }
0660 
0661 QString ImapServerProxy::mailboxFromFolder(const Folder &folder) const
0662 {
0663     Q_ASSERT(!folder.path().isEmpty());
0664     return folder.path();
0665 }
0666 
0667 KAsync::Job<void> ImapServerProxy::fetchFlags(const KIMAP2::ImapSet &set, qint64 changedsince, std::function<void(const Message &)> callback)
0668 {
0669     KIMAP2::FetchJob::FetchScope scope;
0670     scope.mode = KIMAP2::FetchJob::FetchScope::Flags;
0671     scope.changedSince = changedsince;
0672 
0673     return fetch(set, scope, callback);
0674 }
0675 
0676 KAsync::Job<void> ImapServerProxy::fetchMessages(const Folder &folder, qint64 uidNext, std::function<void(const Message &)> callback, std::function<void(int, int)> progress)
0677 {
0678     auto time = QSharedPointer<QTime>::create();
0679     time->start();
0680     return select(folder).then<void, SelectResult>([this, callback, folder, time, progress, uidNext](const SelectResult &selectResult) -> KAsync::Job<void> {
0681         SinkTrace() << "UIDNEXT " << folder.path() << selectResult.uidNext << uidNext;
0682         if (selectResult.uidNext == (uidNext + 1)) {
0683             SinkTrace()<< folder.path() << "Uidnext didn't change, nothing to do.";
0684             return KAsync::null<void>();
0685         }
0686 
0687         SinkTrace() << "Fetching messages from  " << folder.path() << selectResult.uidNext << uidNext;
0688         return fetchHeaders(mailboxFromFolder(folder), (uidNext + 1)).then<void, QVector<qint64>>([this, callback, time, progress, folder](const QVector<qint64> &uidsToFetch){
0689             SinkTrace() << "Fetched headers" << folder.path();
0690             SinkTrace() << "  Total: " << uidsToFetch.size();
0691             SinkTrace() << "  Uids to fetch: " << uidsToFetch;
0692             SinkTrace() << "  Took: " << Sink::Log::TraceTime(time->elapsed());
0693             return fetchMessages(folder, uidsToFetch, false, callback, progress);
0694         });
0695 
0696     });
0697 }
0698 
0699 KAsync::Job<void> ImapServerProxy::fetchMessages(const Folder &folder, const QVector<qint64> &uidsToFetch, bool headersOnly, std::function<void(const Message &)> callback, std::function<void(int, int)> progress)
0700 {
0701     auto time = QSharedPointer<QTime>::create();
0702     time->start();
0703     return select(folder).then<void, SelectResult>([this, callback, folder, time, progress, uidsToFetch, headersOnly](const SelectResult &selectResult) -> KAsync::Job<void> {
0704 
0705         SinkTrace() << "Fetching messages" << folder.path();
0706         SinkTrace() << "  Total: " << uidsToFetch.size();
0707         SinkTrace() << "  Uids to fetch: " << uidsToFetch;
0708         auto totalCount = uidsToFetch.size();
0709         if (progress) {
0710             progress(0, totalCount);
0711         }
0712         if (uidsToFetch.isEmpty()) {
0713             SinkTrace() << "Nothing to fetch";
0714             return KAsync::null<void>();
0715         }
0716         KIMAP2::FetchJob::FetchScope scope;
0717         scope.parts.clear();
0718         if (headersOnly) {
0719             scope.mode = KIMAP2::FetchJob::FetchScope::Headers;
0720         } else {
0721             scope.mode = KIMAP2::FetchJob::FetchScope::Full;
0722         }
0723 
0724         KIMAP2::ImapSet set;
0725         set.add(uidsToFetch);
0726         auto count = QSharedPointer<int>::create();
0727         return fetch(set, scope, [=](const Message &message) {
0728             *count += 1;
0729             if (progress) {
0730                 progress(*count, totalCount);
0731             }
0732             callback(message);
0733         });
0734     })
0735     .then([time]() {
0736         SinkTrace() << "The fetch took: " << Sink::Log::TraceTime(time->elapsed());
0737     });
0738 }
0739 
0740 KAsync::Job<void> ImapServerProxy::fetchMessages(const Folder &folder, std::function<void(const Message &)> callback, std::function<void(int, int)> progress)
0741 {
0742     return fetchMessages(folder, 0, callback, progress);
0743 }
0744 
0745 KAsync::Job<QVector<qint64>> ImapServerProxy::fetchUids(const Folder &folder)
0746 {
0747     return select(mailboxFromFolder(folder)).then(fetchUids());
0748 }