File indexing completed on 2025-02-16 04:50:20

0001 /*
0002     SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
0003     SPDX-FileContributor: Kevin Ottens <kevin@kdab.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "retrievecollectionmetadatatask.h"
0009 
0010 #include <KIMAP/GetAclJob>
0011 #include <KIMAP/GetMetaDataJob>
0012 #include <KIMAP/GetQuotaRootJob>
0013 #include <KIMAP/MyRightsJob>
0014 #include <KIMAP/RFCCodecs>
0015 #include <KIMAP/Session>
0016 #include <KLocalizedString>
0017 
0018 #include "imapresource_debug.h"
0019 
0020 #include "collectionmetadatahelper.h"
0021 #include "imapaclattribute.h"
0022 #include "imapquotaattribute.h"
0023 #include "noselectattribute.h"
0024 #include <Akonadi/CollectionAnnotationsAttribute>
0025 #include <Akonadi/CollectionQuotaAttribute>
0026 #include <Akonadi/EntityDisplayAttribute>
0027 
0028 RetrieveCollectionMetadataTask::RetrieveCollectionMetadataTask(const ResourceStateInterface::Ptr &resource, QObject *parent)
0029     : ResourceTask(CancelIfNoSession, resource, parent)
0030 {
0031 }
0032 
0033 RetrieveCollectionMetadataTask::~RetrieveCollectionMetadataTask() = default;
0034 
0035 void RetrieveCollectionMetadataTask::doStart(KIMAP::Session *session)
0036 {
0037     qCDebug(IMAPRESOURCE_LOG) << collection().remoteId();
0038 
0039     // Prevent fetching metadata from noselect folders.
0040     if (collection().hasAttribute("noselect")) {
0041         NoSelectAttribute *noselect = static_cast<NoSelectAttribute *>(collection().attribute("noselect"));
0042         if (noselect->noSelect()) {
0043             qCDebug(IMAPRESOURCE_LOG) << "No Select folder";
0044             endTaskIfNeeded();
0045             return;
0046         }
0047     }
0048 
0049     m_session = session;
0050     m_collection = collection();
0051     const QString mailBox = mailBoxForCollection(m_collection);
0052     const QStringList capabilities = serverCapabilities();
0053 
0054     m_pendingMetaDataJobs = 0;
0055 
0056     // First get the annotations from the mailbox if it's supported
0057     if (capabilities.contains(QLatin1StringView("METADATA")) || capabilities.contains(QLatin1StringView("ANNOTATEMORE"))) {
0058         auto meta = new KIMAP::GetMetaDataJob(session);
0059         meta->setMailBox(mailBox);
0060         if (capabilities.contains(QLatin1StringView("METADATA"))) {
0061             meta->setServerCapability(KIMAP::MetaDataJobBase::Metadata);
0062             meta->addRequestedEntry("/shared");
0063             meta->setDepth(KIMAP::GetMetaDataJob::AllLevels);
0064         } else {
0065             meta->setServerCapability(KIMAP::MetaDataJobBase::Annotatemore);
0066             meta->addEntry("*", "value.shared");
0067         }
0068         connect(meta, &KJob::result, this, &RetrieveCollectionMetadataTask::onGetMetaDataDone);
0069         m_pendingMetaDataJobs++;
0070         meta->start();
0071     }
0072 
0073     // Get the ACLs from the mailbox if it's supported
0074     if (capabilities.contains(QLatin1StringView("ACL"))) {
0075         auto rights = new KIMAP::MyRightsJob(session);
0076         rights->setMailBox(mailBox);
0077         connect(rights, &KJob::result, this, &RetrieveCollectionMetadataTask::onRightsReceived);
0078         m_pendingMetaDataJobs++;
0079         rights->start();
0080     }
0081 
0082     // Get the QUOTA info from the mailbox if it's supported
0083     if (capabilities.contains(QLatin1StringView("QUOTA"))) {
0084         auto quota = new KIMAP::GetQuotaRootJob(session);
0085         quota->setMailBox(mailBox);
0086         connect(quota, &KJob::result, this, &RetrieveCollectionMetadataTask::onQuotasReceived);
0087         m_pendingMetaDataJobs++;
0088         quota->start();
0089     }
0090 
0091     // the server does not have any of the capabilities needed to get extra info, so this
0092     // step is done here
0093     if (m_pendingMetaDataJobs == 0) {
0094         endTaskIfNeeded();
0095     }
0096 }
0097 
0098 void RetrieveCollectionMetadataTask::onGetMetaDataDone(KJob *job)
0099 {
0100     m_pendingMetaDataJobs--;
0101     if (job->error()) {
0102         qCWarning(IMAPRESOURCE_LOG) << "Get metadata failed: " << job->errorString();
0103         endTaskIfNeeded();
0104         return; // Well, no metadata for us then...
0105     }
0106 
0107     auto meta = qobject_cast<KIMAP::GetMetaDataJob *>(job);
0108     QMap<QByteArray, QByteArray> rawAnnotations = meta->allMetaData();
0109 
0110     // filter out unused and annoying Cyrus annotation /vendor/cmu/cyrus-imapd/lastupdate
0111     // which contains the current date and time and thus constantly changes for no good
0112     // reason which triggers a change notification and thus a bunch of Akonadi operations
0113     rawAnnotations.remove("/shared/vendor/cmu/cyrus-imapd/lastupdate");
0114     rawAnnotations.remove("/private/vendor/cmu/cyrus-imapd/lastupdate");
0115 
0116     // Store the mailbox metadata
0117     auto annotationsAttribute = m_collection.attribute<Akonadi::CollectionAnnotationsAttribute>(Akonadi::Collection::AddIfMissing);
0118     const QMap<QByteArray, QByteArray> oldAnnotations = annotationsAttribute->annotations();
0119     if (oldAnnotations != rawAnnotations) {
0120         annotationsAttribute->setAnnotations(rawAnnotations);
0121     }
0122 
0123     endTaskIfNeeded();
0124 }
0125 
0126 void RetrieveCollectionMetadataTask::onGetAclDone(KJob *job)
0127 {
0128     m_pendingMetaDataJobs--;
0129     if (job->error()) {
0130         qCWarning(IMAPRESOURCE_LOG) << "GetACL failed: " << job->errorString();
0131         endTaskIfNeeded();
0132         return; // Well, no metadata for us then...
0133     }
0134 
0135     auto acl = qobject_cast<KIMAP::GetAclJob *>(job);
0136 
0137     // Store the mailbox ACLs
0138     auto const aclAttribute = m_collection.attribute<Akonadi::ImapAclAttribute>(Akonadi::Collection::AddIfMissing);
0139     const QMap<QByteArray, KIMAP::Acl::Rights> oldRights = aclAttribute->rights();
0140     if (oldRights != acl->allRights()) {
0141         aclAttribute->setRights(acl->allRights());
0142     }
0143 
0144     endTaskIfNeeded();
0145 }
0146 
0147 void RetrieveCollectionMetadataTask::onRightsReceived(KJob *job)
0148 {
0149     m_pendingMetaDataJobs--;
0150     if (job->error()) {
0151         qCWarning(IMAPRESOURCE_LOG) << "MyRights failed: " << job->errorString();
0152         endTaskIfNeeded();
0153         return; // Well, no metadata for us then...
0154     }
0155 
0156     auto rightsJob = qobject_cast<KIMAP::MyRightsJob *>(job);
0157 
0158     const KIMAP::Acl::Rights imapRights = rightsJob->rights();
0159 
0160     // Default value in case we have nothing better available
0161     KIMAP::Acl::Rights parentRights = KIMAP::Acl::CreateMailbox | KIMAP::Acl::Create;
0162 
0163     // FIXME I don't think we have the parent's acl's available
0164     if (collection().parentCollection().attribute<Akonadi::ImapAclAttribute>()) {
0165         parentRights = myRights(collection().parentCollection());
0166     }
0167 
0168     //  qCDebug(IMAPRESOURCE_LOG) << collection.remoteId()
0169     //                 << "imapRights:" << imapRights
0170     //                 << "newRights:" << newRights
0171     //                 << "oldRights:" << collection.rights();
0172 
0173     const bool isNewCollection = !m_collection.hasAttribute<Akonadi::ImapAclAttribute>();
0174     const bool accessRevoked = CollectionMetadataHelper::applyRights(m_collection, imapRights, parentRights);
0175     if (accessRevoked && !isNewCollection) {
0176         // write access revoked
0177         const QString collectionName = m_collection.displayName();
0178 
0179         showInformationDialog(i18n("<p>Your access rights to folder <b>%1</b> have been restricted, "
0180                                    "it will no longer be possible to add messages to this folder.</p>",
0181                                    collectionName),
0182                               i18n("Access rights revoked"),
0183                               QStringLiteral("ShowRightsRevokedWarning"));
0184     }
0185 
0186     // Store the mailbox ACLs
0187     auto aclAttribute = m_collection.attribute<Akonadi::ImapAclAttribute>(Akonadi::Collection::AddIfMissing);
0188     const KIMAP::Acl::Rights oldRights = aclAttribute->myRights();
0189     if (oldRights != imapRights) {
0190         aclAttribute->setMyRights(imapRights);
0191     }
0192 
0193     // The a right is required to list acl's
0194     if (imapRights & KIMAP::Acl::Admin) {
0195         auto acl = new KIMAP::GetAclJob(m_session);
0196         acl->setMailBox(mailBoxForCollection(m_collection));
0197         connect(acl, &KJob::result, this, &RetrieveCollectionMetadataTask::onGetAclDone);
0198         m_pendingMetaDataJobs++;
0199         acl->start();
0200     }
0201 
0202     endTaskIfNeeded();
0203 }
0204 
0205 void RetrieveCollectionMetadataTask::onQuotasReceived(KJob *job)
0206 {
0207     m_pendingMetaDataJobs--;
0208     const QString &mailBox = mailBoxForCollection(m_collection);
0209     if (job->error()) {
0210         qCWarning(IMAPRESOURCE_LOG) << "Quota retrieval for mailbox " << mailBox << " failed: " << job->errorString();
0211         endTaskIfNeeded();
0212         return; // Well, no metadata for us then...
0213     }
0214 
0215     auto quotaJob = qobject_cast<KIMAP::GetQuotaRootJob *>(job);
0216 
0217     QList<QByteArray> allRoots = quotaJob->roots();
0218     QList<QByteArray> newRoots;
0219     QList<QMap<QByteArray, qint64>> newLimits;
0220     QList<QMap<QByteArray, qint64>> newUsages;
0221     qint64 newCurrent = -1;
0222     qint64 newMax = -1;
0223     newRoots.reserve(allRoots.count());
0224     newLimits.reserve(allRoots.count());
0225     newUsages.reserve(allRoots.count());
0226 
0227     for (const QByteArray &root : std::as_const(allRoots)) {
0228         const QMap<QByteArray, qint64> limit = quotaJob->allLimits(root);
0229         const QMap<QByteArray, qint64> usage = quotaJob->allUsages(root);
0230 
0231         // Process IMAP Quota roots with associated quotas only
0232         if (!limit.isEmpty() && !usage.isEmpty()) {
0233             newRoots << root;
0234             newLimits << limit;
0235             newUsages << usage;
0236 
0237             const QString &decodedRoot = QString::fromUtf8(KIMAP::decodeImapFolderName(root));
0238 
0239             if (decodedRoot == mailBox) {
0240                 newCurrent = newUsages.last()["STORAGE"] * 1024;
0241                 newMax = newLimits.last()["STORAGE"] * 1024;
0242             }
0243         }
0244     }
0245 
0246     // If usage and limit were not set, retrieve them from the first root, if exists
0247     if ((newCurrent == -1 && newMax == -1) && !newRoots.isEmpty()) {
0248         newCurrent = newUsages.first()["STORAGE"] * 1024;
0249         newMax = newLimits.first()["STORAGE"] * 1024;
0250     }
0251 
0252     // Store the mailbox IMAP Quotas
0253     auto imapQuotaAttribute = m_collection.attribute<Akonadi::ImapQuotaAttribute>(Akonadi::Collection::AddIfMissing);
0254     const QList<QByteArray> oldRoots = imapQuotaAttribute->roots();
0255     const QList<QMap<QByteArray, qint64>> oldLimits = imapQuotaAttribute->limits();
0256     const QList<QMap<QByteArray, qint64>> oldUsages = imapQuotaAttribute->usages();
0257 
0258     if (oldRoots != newRoots || oldLimits != newLimits || oldUsages != newUsages) {
0259         imapQuotaAttribute->setQuotas(newRoots, newLimits, newUsages);
0260     }
0261 
0262     // Store the collection Quota
0263     auto quotaAttribute = m_collection.attribute<Akonadi::CollectionQuotaAttribute>(Akonadi::Collection::AddIfMissing);
0264     qint64 oldCurrent = quotaAttribute->currentValue();
0265     qint64 oldMax = quotaAttribute->maximumValue();
0266 
0267     if (oldCurrent != newCurrent || oldMax != newMax) {
0268         quotaAttribute->setCurrentValue(newCurrent);
0269         quotaAttribute->setMaximumValue(newMax);
0270     }
0271 
0272     endTaskIfNeeded();
0273 }
0274 
0275 void RetrieveCollectionMetadataTask::endTaskIfNeeded()
0276 {
0277     if (m_pendingMetaDataJobs <= 0) {
0278         collectionAttributesRetrieved(m_collection);
0279     }
0280 }
0281 
0282 #include "moc_retrievecollectionmetadatatask.cpp"