1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
2 
3    This file is part of the Trojita Qt IMAP e-mail client,
4    http://trojita.flaska.net/
5 
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.
13 
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.
18 
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/>.
21 */
22 
23 #include <algorithm>
24 #include <QTextStream>
25 #include "Common/FindWithUnknown.h"
26 #include "Common/InvokeMethod.h"
27 #include "Common/MetaTypes.h"
28 #include "Imap/Encoders.h"
29 #include "Imap/Parser/Rfc5322HeaderParser.h"
30 #include "Imap/Tasks/KeepMailboxOpenTask.h"
31 #include "UiUtils/Formatting.h"
32 #include "ItemRoles.h"
33 #include "MailboxTree.h"
34 #include "Model.h"
35 #include "SpecialFlagNames.h"
36 #include <QtDebug>
37 
38 
39 namespace Imap
40 {
41 namespace Mailbox
42 {
43 
TreeItem(TreeItem * parent)44 TreeItem::TreeItem(TreeItem *parent): m_parent(parent)
45 {
46     // These just have to be present in the context of TreeItem, otherwise they couldn't access the protected members
47     static_assert(static_cast<intptr_t>(alignof(TreeItem)) > TreeItem::TagMask,
48                   "class TreeItem must be aligned at at least four bytes due to the FetchingState optimization");
49     static_assert(DONE <= TagMask, "Invalid masking for pointer tag access");
50 }
51 
~TreeItem()52 TreeItem::~TreeItem()
53 {
54     qDeleteAll(m_children);
55 }
56 
childrenCount(Model * const model)57 unsigned int TreeItem::childrenCount(Model *const model)
58 {
59     fetch(model);
60     return m_children.size();
61 }
62 
child(int offset,Model * const model)63 TreeItem *TreeItem::child(int offset, Model *const model)
64 {
65     fetch(model);
66     if (offset >= 0 && offset < m_children.size())
67         return m_children[ offset ];
68     else
69         return 0;
70 }
71 
row() const72 int TreeItem::row() const
73 {
74     return parent() ? parent()->m_children.indexOf(const_cast<TreeItem *>(this)) : 0;
75 }
76 
setChildren(const TreeItemChildrenList & items)77 TreeItemChildrenList TreeItem::setChildren(const TreeItemChildrenList &items)
78 {
79     auto res = m_children;
80     m_children = items;
81     setFetchStatus(DONE);
82     return res;
83 }
84 
isUnavailable() const85 bool TreeItem::isUnavailable() const
86 {
87     return accessFetchStatus() == UNAVAILABLE;
88 }
89 
columnCount()90 unsigned int TreeItem::columnCount()
91 {
92     return 1;
93 }
94 
specialColumnPtr(int row,int column) const95 TreeItem *TreeItem::specialColumnPtr(int row, int column) const
96 {
97     Q_UNUSED(row);
98     Q_UNUSED(column);
99     return 0;
100 }
101 
toIndex(Model * const model) const102 QModelIndex TreeItem::toIndex(Model *const model) const
103 {
104     Q_ASSERT(model);
105     if (this == model->m_mailboxes)
106         return QModelIndex();
107     // void* != const void*, but I believe that it's safe in this context
108     return model->createIndex(row(), 0, const_cast<TreeItem *>(this));
109 }
110 
111 
TreeItemMailbox(TreeItem * parent)112 TreeItemMailbox::TreeItemMailbox(TreeItem *parent): TreeItem(parent), maintainingTask(0)
113 {
114     m_children.prepend(new TreeItemMsgList(this));
115 }
116 
TreeItemMailbox(TreeItem * parent,Responses::List response)117 TreeItemMailbox::TreeItemMailbox(TreeItem *parent, Responses::List response):
118     TreeItem(parent), m_metadata(response.mailbox, response.separator, QStringList()), maintainingTask(0)
119 {
120     for (QStringList::const_iterator it = response.flags.constBegin(); it != response.flags.constEnd(); ++it)
121         m_metadata.flags.append(it->toUpper());
122     m_children.prepend(new TreeItemMsgList(this));
123 }
124 
~TreeItemMailbox()125 TreeItemMailbox::~TreeItemMailbox()
126 {
127     if (maintainingTask) {
128         maintainingTask->dieIfInvalidMailbox();
129     }
130 }
131 
fromMetadata(TreeItem * parent,const MailboxMetadata & metadata)132 TreeItemMailbox *TreeItemMailbox::fromMetadata(TreeItem *parent, const MailboxMetadata &metadata)
133 {
134     TreeItemMailbox *res = new TreeItemMailbox(parent);
135     res->m_metadata = metadata;
136     return res;
137 }
138 
fetch(Model * const model)139 void TreeItemMailbox::fetch(Model *const model)
140 {
141     fetchWithCacheControl(model, false);
142 }
143 
fetchWithCacheControl(Model * const model,bool forceReload)144 void TreeItemMailbox::fetchWithCacheControl(Model *const model, bool forceReload)
145 {
146     if (fetched() || isUnavailable())
147         return;
148 
149     if (hasNoChildMailboxesAlreadyKnown()) {
150         setFetchStatus(DONE);
151         return;
152     }
153 
154     if (! loading()) {
155         setFetchStatus(LOADING);
156         QModelIndex mailboxIndex = toIndex(model);
157         CALL_LATER(model, askForChildrenOfMailbox, Q_ARG(QModelIndex, mailboxIndex),
158                    Q_ARG(Imap::Mailbox::CacheLoadingMode, forceReload ? LOAD_FORCE_RELOAD : LOAD_CACHED_IS_OK));
159     }
160 }
161 
rescanForChildMailboxes(Model * const model)162 void TreeItemMailbox::rescanForChildMailboxes(Model *const model)
163 {
164     if (accessFetchStatus() != LOADING) {
165         setFetchStatus(NONE);
166         fetchWithCacheControl(model, true);
167     }
168 }
169 
rowCount(Model * const model)170 unsigned int TreeItemMailbox::rowCount(Model *const model)
171 {
172     fetch(model);
173     return m_children.size();
174 }
175 
data(Model * const model,int role)176 QVariant TreeItemMailbox::data(Model *const model, int role)
177 {
178     switch (role) {
179     case RoleIsFetched:
180         return fetched();
181     case RoleIsUnavailable:
182         return isUnavailable();
183     };
184 
185     if (!parent())
186         return QVariant();
187 
188     TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(m_children[0]);
189     Q_ASSERT(list);
190 
191     switch (role) {
192     case Qt::DisplayRole:
193     {
194         // this one is used only for a dumb view attached to the Model
195         QString res = separator().isEmpty() ? mailbox() : mailbox().split(separator(), QString::SkipEmptyParts).last();
196         return loading() ? res + QLatin1String(" [loading]") : res;
197     }
198     case RoleShortMailboxName:
199         return separator().isEmpty() ? mailbox() : mailbox().split(separator(), QString::SkipEmptyParts).last();
200     case RoleMailboxName:
201         return mailbox();
202     case RoleMailboxSeparator:
203         return separator();
204     case RoleMailboxHasChildMailboxes:
205         return hasChildMailboxes(model);
206     case RoleMailboxIsINBOX:
207         return mailbox().toUpper() == QLatin1String("INBOX");
208     case RoleMailboxIsSelectable:
209         return isSelectable();
210     case RoleMailboxNumbersFetched:
211         return list->numbersFetched();
212     case RoleTotalMessageCount:
213     {
214         if (! isSelectable())
215             return QVariant();
216         // At first, register that request for count
217         int res = list->totalMessageCount(model);
218         // ...and now that it's been sent, display a number if it's available, or if it was available before.
219         // It's better to return a value which is maybe already obsolete than to hide a value which might
220         // very well be still correct.
221         return list->numbersFetched() || res != -1 ? QVariant(res) : QVariant();
222     }
223     case RoleUnreadMessageCount:
224     {
225         if (! isSelectable())
226             return QVariant();
227         // This one is similar to the case of RoleTotalMessageCount
228         int res = list->unreadMessageCount(model);
229         return list->numbersFetched() || res != -1 ? QVariant(res): QVariant();
230     }
231     case RoleRecentMessageCount:
232     {
233         if (! isSelectable())
234             return QVariant();
235         // see above
236         int res = list->recentMessageCount(model);
237         return list->numbersFetched() || res != -1 ? QVariant(res): QVariant();
238     }
239     case RoleMailboxItemsAreLoading:
240         return list->loading() || (isSelectable() && ! list->numbersFetched());
241     case RoleMailboxUidValidity:
242     {
243         list->fetch(model);
244         return list->fetched() ? QVariant(syncState.uidValidity()) : QVariant();
245     }
246     case RoleMailboxIsSubscribed:
247         return QVariant::fromValue<bool>(m_metadata.flags.contains(QStringLiteral("\\SUBSCRIBED")));
248     default:
249         return QVariant();
250     }
251 }
252 
hasChildren(Model * const model)253 bool TreeItemMailbox::hasChildren(Model *const model)
254 {
255     Q_UNUSED(model);
256     return true; // we have that "messages" thing built in
257 }
258 
259 QLatin1String TreeItemMailbox::flagNoInferiors("\\NOINFERIORS");
260 QLatin1String TreeItemMailbox::flagHasNoChildren("\\HASNOCHILDREN");
261 QLatin1String TreeItemMailbox::flagHasChildren("\\HASCHILDREN");
262 
hasNoChildMailboxesAlreadyKnown()263 bool TreeItemMailbox::hasNoChildMailboxesAlreadyKnown()
264 {
265     if (m_metadata.flags.contains(flagNoInferiors) ||
266         (m_metadata.flags.contains(flagHasNoChildren) &&
267          ! m_metadata.flags.contains(flagHasChildren)))
268         return true;
269     else
270         return false;
271 }
272 
hasChildMailboxes(Model * const model)273 bool TreeItemMailbox::hasChildMailboxes(Model *const model)
274 {
275     if (fetched() || isUnavailable()) {
276         return m_children.size() > 1;
277     } else if (hasNoChildMailboxesAlreadyKnown()) {
278         return false;
279     } else if (m_metadata.flags.contains(flagHasChildren) && ! m_metadata.flags.contains(flagHasNoChildren)) {
280         return true;
281     } else {
282         fetch(model);
283         return m_children.size() > 1;
284     }
285 }
286 
child(const int offset,Model * const model)287 TreeItem *TreeItemMailbox::child(const int offset, Model *const model)
288 {
289     // accessing TreeItemMsgList doesn't need fetch()
290     if (offset == 0)
291         return m_children[ 0 ];
292 
293     return TreeItem::child(offset, model);
294 }
295 
setChildren(const TreeItemChildrenList & items)296 TreeItemChildrenList TreeItemMailbox::setChildren(const TreeItemChildrenList &items)
297 {
298     // This function has to be special because we want to preserve m_children[0]
299 
300     TreeItemMsgList *msgList = dynamic_cast<TreeItemMsgList *>(m_children[0]);
301     Q_ASSERT(msgList);
302     m_children.erase(m_children.begin());
303 
304     auto list = TreeItem::setChildren(items);  // this also adjusts m_loading and m_fetched
305     m_children.prepend(msgList);
306 
307     return list;
308 }
309 
handleFetchResponse(Model * const model,const Responses::Fetch & response,QList<TreeItemPart * > & changedParts,TreeItemMessage * & changedMessage,bool usingQresync)310 void TreeItemMailbox::handleFetchResponse(Model *const model,
311         const Responses::Fetch &response,
312         QList<TreeItemPart *> &changedParts,
313         TreeItemMessage *&changedMessage, bool usingQresync)
314 {
315     TreeItemMsgList *list = static_cast<TreeItemMsgList *>(m_children[0]);
316 
317     Responses::Fetch::dataType::const_iterator uidRecord = response.data.find("UID");
318 
319     // Previously, we would ignore any FETCH responses until we are fully synced. This is rather hard do to "properly",
320     // though.
321     // What we want to achieve is to never store data into a "wrong" message. Theoretically, we are prone to just this
322     // in case the server sends us unsolicited data before we are fully synced. When this happens for flags, it's a pretty
323     // harmless operation as we're going to re-fetch the flags for the concerned part of mailbox anyway (even with CONDSTORE,
324     // and this is never an issue with QRESYNC).
325     // It's worse when the data refer to some immutable piece of information like the bodystructure or body parts.
326     // If that happens, then we have to actively prevent the data from being stored because we cannot know whether we would
327     // be putting it into a correct bucket^Hmessage.
328     bool ignoreImmutableData = !list->fetched() && uidRecord == response.data.constEnd();
329 
330     int number = response.number - 1;
331     if (number < 0 || number >= list->m_children.size())
332         throw UnknownMessageIndex(QStringLiteral("Got FETCH that is out of bounds -- got %1 messages").arg(
333                                       QString::number(list->m_children.size())).toUtf8().constData(), response);
334 
335     TreeItemMessage *message = static_cast<TreeItemMessage *>(list->child(number, model));
336 
337     // At first, have a look at the response and check the UID of the message
338     if (uidRecord != response.data.constEnd()) {
339         uint receivedUid = static_cast<const Responses::RespData<uint>&>(*(uidRecord.value())).data;
340         if (receivedUid == 0) {
341             throw MailboxException(QStringLiteral("Server claims that message #%1 has UID 0")
342                                    .arg(QString::number(response.number)).toUtf8().constData(), response);
343         } else if (message->uid() == receivedUid) {
344             // That's what we expect -> do nothing
345         } else if (message->uid() == 0) {
346             // This is the first time we see the UID, so let's take a note
347             message->m_uid = receivedUid;
348             changedMessage = message;
349             if (message->loading()) {
350                 // The Model tried to ask for data for this message. That couldn't succeeded because the UID
351                 // wasn't known at that point, so let's ask now
352                 //
353                 // FIXME: tweak this to keep a high watermark of "highest UID we requested an ENVELOPE for",
354                 // issue bulk fetches in the same manner as we do the UID FETCH (FLAGS) when discovering UIDs,
355                 // and at this place in code, only ask for the metadata when the UID is higher than the watermark.
356                 // Optionally, simply ask for the ENVELOPE etc along with the FLAGS upon new message arrivals, maybe
357                 // with some limit on the number of pending fetches. And make that dapandent on online/expensive modes.
358                 message->setFetchStatus(NONE);
359                 message->fetch(model);
360             }
361             if (syncState.uidNext() <= receivedUid) {
362                 // Try to guess the UIDNEXT. We have to take an educated guess here, and I believe that this approach
363                 // at least is not wrong. The server won't tell us the UIDNEXT (well, it could, but it doesn't have to),
364                 // the only way of asking for it is via STATUS which is not allowed to reference the current mailbox and
365                 // even if it was, it wouldn't be atomic. So, what could the UIDNEXT possibly be? It can't be smaller
366                 // than the UID_of_highest_message, and it can't be the same, either, so it really has to be higher.
367                 // Let's just increment it by one, this is our lower bound.
368                 // Not guessing the UIDNEXT correctly would result at decreased performance at the next sync, and we
369                 // can't really do better -> let's just set it now, along with the UID mapping.
370                 syncState.setUidNext(receivedUid + 1);
371                 list->setFetchStatus(LOADING);
372             }
373         } else {
374             throw MailboxException(QStringLiteral("FETCH response: UID consistency error for message #%1 -- expected UID %2, got UID %3").arg(
375                                        QString::number(response.number), QString::number(message->uid()), QString::number(receivedUid)
376                                        ).toUtf8().constData(), response);
377         }
378     } else if (! message->uid()) {
379         qDebug() << "FETCH: received a FETCH response for message #" << response.number << "whose UID is not yet known. This sucks.";
380         QList<uint> uidsInMailbox;
381         Q_FOREACH(TreeItem *node, list->m_children) {
382             uidsInMailbox << static_cast<TreeItemMessage *>(node)->uid();
383         }
384         qDebug() << "UIDs in the mailbox now: " << uidsInMailbox;
385     }
386 
387     bool updatedFlags = false;
388 
389     for (Responses::Fetch::dataType::const_iterator it = response.data.begin(); it != response.data.end(); ++ it) {
390         if (it.key() == "UID") {
391             // established above
392             Q_ASSERT(static_cast<const Responses::RespData<uint>&>(*(it.value())).data == message->uid());
393         } else if (it.key() == "FLAGS") {
394             // Only emit signals when the flags have actually changed
395             QStringList newFlags = model->normalizeFlags(static_cast<const Responses::RespData<QStringList>&>(*(it.value())).data);
396             bool forceChange = !message->m_flagsHandled || (message->m_flags != newFlags);
397             message->setFlags(list, newFlags);
398             if (forceChange) {
399                 updatedFlags = true;
400                 changedMessage = message;
401             }
402         } else if (it.key() == "MODSEQ") {
403             quint64 num = static_cast<const Responses::RespData<quint64>&>(*(it.value())).data;
404             if (num > syncState.highestModSeq()) {
405                 syncState.setHighestModSeq(num);
406                 if (list->accessFetchStatus() == DONE) {
407                     // This means that everything is known already, so we are by definition OK to save stuff to disk.
408                     // We can also skip rebuilding the UID map and save just the HIGHESTMODSEQ, i.e. the SyncState.
409                     model->cache()->setMailboxSyncState(mailbox(), syncState);
410                 } else {
411                     // it's already marked as dirty -> nothing to do here
412                 }
413             }
414         } else if (ignoreImmutableData) {
415             QByteArray buf;
416             QTextStream ss(&buf);
417             ss << response;
418             ss.flush();
419             qDebug() << "Ignoring FETCH response to a mailbox that isn't synced yet:" << buf;
420             continue;
421         } else if (it.key() == "ENVELOPE") {
422             message->data()->setEnvelope(static_cast<const Responses::RespData<Message::Envelope>&>(*(it.value())).data);
423             changedMessage = message;
424         } else if (it.key() == "BODYSTRUCTURE") {
425             if (message->data()->gotRemeberedBodyStructure() || message->fetched()) {
426                 // The message structure is already known, so we are free to ignore it
427             } else {
428                 // We had no idea about the structure of the message
429 
430                 // At first, save the bodystructure. This is needed so that our overridden rowCount() works properly.
431                 // (The rowCount() gets called through QAIM::beginInsertRows(), for example.)
432                 auto xtbIt = response.data.constFind("x-trojita-bodystructure");
433                 Q_ASSERT(xtbIt != response.data.constEnd());
434                 message->data()->setRememberedBodyStructure(
435                         static_cast<const Responses::RespData<QByteArray>&>(*(xtbIt.value())).data);
436 
437                 // Now insert the children. We're of course assuming that the TreeItemMessage is now empty.
438                 auto newChildren = static_cast<const Message::AbstractMessage &>(*(it.value())).createTreeItems(message);
439                 Q_ASSERT(!newChildren.isEmpty());
440                 Q_ASSERT(message->m_children.isEmpty());
441                 QModelIndex messageIdx = message->toIndex(model);
442                 model->beginInsertRows(messageIdx, 0, newChildren.size() - 1);
443                 message->setChildren(newChildren);
444                 model->endInsertRows();
445             }
446         } else if (it.key() == "x-trojita-bodystructure") {
447             // do nothing here, it's been already taken care of from the BODYSTRUCTURE handler
448         } else if (it.key() == "RFC822.SIZE") {
449             message->data()->setSize(static_cast<const Responses::RespData<quint64>&>(*(it.value())).data);
450         } else if (it.key().startsWith("BODY[HEADER.FIELDS (")) {
451             // Process any headers found in any such response bit
452             const QByteArray &rawHeaders = static_cast<const Responses::RespData<QByteArray>&>(*(it.value())).data;
453             message->processAdditionalHeaders(model, rawHeaders);
454             changedMessage = message;
455         } else if (it.key().startsWith("BODY[") || it.key().startsWith("BINARY[")) {
456             if (it.key()[ it.key().size() - 1 ] != ']')
457                 throw UnknownMessageIndex("Can't parse such BODY[]/BINARY[]", response);
458             TreeItemPart *part = partIdToPtr(model, message, it.key());
459             if (! part)
460                 throw UnknownMessageIndex("Got BODY[]/BINARY[] fetch that did not resolve to any known part", response);
461             const QByteArray &data = static_cast<const Responses::RespData<QByteArray>&>(*(it.value())).data;
462             if (it.key().startsWith("BODY[")) {
463 
464                 // Check whether we are supposed to be loading the raw, undecoded part as well.
465                 // The check has to be done via a direct pointer access to m_partRaw to make sure that it does not
466                 // get instantiated when not actually needed.
467                 if (part->m_partRaw && part->m_partRaw->loading()) {
468                     part->m_partRaw->m_data = data;
469                     part->m_partRaw->setFetchStatus(DONE);
470                     changedParts.append(part->m_partRaw);
471                     if (message->uid()) {
472                         model->cache()->forgetMessagePart(mailbox(), message->uid(), part->partId());
473                         model->cache()->setMsgPart(mailbox(), message->uid(), part->partId() + ".X-RAW", data);
474                     }
475                 }
476 
477                 // Do not overwrite the part data if we were not asked to fetch it.
478                 // One possibility is that it's already there because it was fetched before. The second option is that
479                 // we were in fact asked to only fetch the raw data and the user is not itnerested in the processed data at all.
480                 if (part->loading()) {
481                     // got to decode the part data by hand
482                     Imap::decodeContentTransferEncoding(data, part->encoding(), part->dataPtr());
483                     part->setFetchStatus(DONE);
484                     changedParts.append(part);
485                     if (message->uid()
486                             && model->cache()->messagePart(mailbox(), message->uid(), part->partId() + ".X-RAW").isNull()) {
487                         // Do not store the data into cache if the raw data are already there
488                         model->cache()->setMsgPart(mailbox(), message->uid(), part->partId(), part->m_data);
489                     }
490                 }
491 
492             } else {
493                 // A BINARY FETCH item is already decoded for us, yay
494                 part->m_data = data;
495                 part->setFetchStatus(DONE);
496                 changedParts.append(part);
497                 if (message->uid()) {
498                     model->cache()->setMsgPart(mailbox(), message->uid(), part->partId(), part->m_data);
499                 }
500             }
501         } else if (it.key() == "INTERNALDATE") {
502             message->data()->setInternalDate(static_cast<const Responses::RespData<QDateTime>&>(*(it.value())).data);
503         } else {
504             qDebug() << "TreeItemMailbox::handleFetchResponse: unknown FETCH identifier" << it.key();
505         }
506     }
507     if (message->uid()) {
508         if (message->data()->isComplete() && model->cache()->messageMetadata(mailbox(), message->uid()).uid == 0) {
509              model->cache()->setMessageMetadata(
510                          mailbox(), message->uid(),
511                          Imap::Mailbox::AbstractCache::MessageDataBundle(
512                              message->uid(),
513                              message->data()->envelope(),
514                              message->data()->internalDate(),
515                              message->data()->size(),
516                              message->data()->rememberedBodyStructure(),
517                              message->data()->hdrReferences(),
518                              message->data()->hdrListPost(),
519                              message->data()->hdrListPostNo()
520                          ));
521              message->setFetchStatus(DONE);
522         }
523         if (updatedFlags) {
524             model->cache()->setMsgFlags(mailbox(), message->uid(), message->m_flags);
525         }
526     }
527 }
528 
529 /** @short Save the sync state and the UID mapping into the cache
530 
531 Please note that FLAGS are still being updated "asynchronously", i.e. immediately when an update arrives. The motivation
532 behind this is that both SyncState and UID mapping just absolutely have to be kept in sync due to the way they are used where
533 our syncing code simply expects both to match each other. There cannot ever be any 0 UIDs in the saved UID mapping, and the
534 number in EXISTS and the amount of cached UIDs is not supposed to differ or all bets are off.
535 
536 The flags, on the other hand, are not critical -- if a message gets saved with the correct flags "too early", i.e. before
537 the corresponding SyncState and/or UIDs are saved, the wors case which could possibly happen are data which do not match the
538 old state any longer. But the old state is not important anyway because it's already gone on the server.
539 */
saveSyncStateAndUids(Model * model)540 void TreeItemMailbox::saveSyncStateAndUids(Model * model)
541 {
542     TreeItemMsgList *list = dynamic_cast<TreeItemMsgList*>(m_children[0]);
543     if (list->m_unreadMessageCount != -1) {
544         syncState.setUnSeenCount(list->m_unreadMessageCount);
545     }
546     if (list->m_recentMessageCount != -1) {
547         syncState.setRecent(list->m_recentMessageCount);
548     }
549     model->cache()->setMailboxSyncState(mailbox(), syncState);
550     model->saveUidMap(list);
551     list->setFetchStatus(DONE);
552 }
553 
554 /** @short Process the EXPUNGE response when the UIDs are already synced */
handleExpunge(Model * const model,const Responses::NumberResponse & resp)555 void TreeItemMailbox::handleExpunge(Model *const model, const Responses::NumberResponse &resp)
556 {
557     Q_ASSERT(resp.kind == Responses::EXPUNGE);
558     TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(m_children[ 0 ]);
559     Q_ASSERT(list);
560     if (resp.number > static_cast<uint>(list->m_children.size()) || resp.number == 0) {
561         throw UnknownMessageIndex("EXPUNGE references message number which is out-of-bounds");
562     }
563     uint offset = resp.number - 1;
564 
565     model->beginRemoveRows(list->toIndex(model), offset, offset);
566     auto it = list->m_children.begin() + offset;
567     TreeItemMessage *message = static_cast<TreeItemMessage *>(*it);
568     list->m_children.erase(it);
569     model->cache()->clearMessage(static_cast<TreeItemMailbox *>(list->parent())->mailbox(), message->uid());
570     for (int i = offset; i < list->m_children.size(); ++i) {
571         --static_cast<TreeItemMessage *>(list->m_children[i])->m_offset;
572     }
573     model->endRemoveRows();
574 
575     --list->m_totalMessageCount;
576     list->recalcVariousMessageCountsOnExpunge(const_cast<Model *>(model), message);
577 
578     delete message;
579 
580     // The UID map is not synced at this time, though, and we defer a decision on when to do this to the context
581     // of the task which invoked this method. The idea is that this task has a better insight for potentially
582     // batching these changes to prevent useless hammering of the saveUidMap() etc.
583     // Previously, the code would simetimes do this twice in a row, which is kinda suboptimal...
584 }
585 
handleVanished(Model * const model,const Responses::Vanished & resp)586 void TreeItemMailbox::handleVanished(Model *const model, const Responses::Vanished &resp)
587 {
588     TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(m_children[ 0 ]);
589     Q_ASSERT(list);
590     QModelIndex listIndex = list->toIndex(model);
591 
592     auto uids = resp.uids;
593     qSort(uids);
594     // Remove duplicates -- even that garbage can be present in a perfectly valid VANISHED :(
595     uids.erase(std::unique(uids.begin(), uids.end()), uids.end());
596 
597     auto it = list->m_children.end();
598     while (!uids.isEmpty()) {
599         // We have to process each UID separately because the UIDs in the mailbox are not necessarily present
600         // in a continuous range; zeros might be present
601         uint uid = uids.last();
602         uids.pop_back();
603 
604         if (uid == 0) {
605             qDebug() << "VANISHED informs about removal of UID zero...";
606             model->logTrace(listIndex.parent(), Common::LOG_MAILBOX_SYNC, QStringLiteral("TreeItemMailbox::handleVanished"),
607                             QStringLiteral("VANISHED contains UID zero for increased fun"));
608             break;
609         }
610 
611         if (list->m_children.isEmpty()) {
612             // Well, it'd be cool to throw an exception here but VANISHED is free to contain references to UIDs which are not here
613             // at all...
614             qDebug() << "VANISHED attempted to remove too many messages";
615             model->logTrace(listIndex.parent(), Common::LOG_MAILBOX_SYNC, QStringLiteral("TreeItemMailbox::handleVanished"),
616                             QStringLiteral("VANISHED attempted to remove too many messages"));
617             break;
618         }
619 
620         // Find a highest message with UID zero such as no message with non-zero UID higher than the current UID exists
621         // at a position after the target message
622         it = model->findMessageOrNextOneByUid(list, uid);
623 
624         if (it == list->m_children.end()) {
625             // this is a legitimate situation, the UID of the last message in the mailbox which is getting expunged right now
626             // could very well be not know at this point
627             --it;
628         }
629         // there's a special case above guarding against an empty list
630         Q_ASSERT(it >= list->m_children.begin());
631 
632         TreeItemMessage *msgCandidate = static_cast<TreeItemMessage*>(*it);
633         if (msgCandidate->uid() == uid) {
634             // will be deleted
635         } else if (resp.earlier == Responses::Vanished::EARLIER) {
636             // We don't have any such UID in our UID mapping, so we can safely ignore this one
637             continue;
638         } else if (msgCandidate->uid() == 0) {
639             // will be deleted
640         } else {
641             if (it != list->m_children.begin()) {
642                 --it;
643                 msgCandidate = static_cast<TreeItemMessage*>(*it);
644                 if (msgCandidate->uid() == 0) {
645                     // will be deleted
646                 } else {
647                     // VANISHED is free to refer to a non-existing UID...
648                     QString str;
649                     QTextStream ss(&str);
650                     ss << "VANISHED refers to UID " << uid << " which wasn't found in the mailbox (found adjacent UIDs " <<
651                           msgCandidate->uid() << " and " << static_cast<TreeItemMessage*>(*(it + 1))->uid() << " with " <<
652                           static_cast<TreeItemMessage*>(*(list->m_children.end() - 1))->uid() << " at the end)";
653                     ss.flush();
654                     qDebug() << str.toUtf8().constData();
655                     model->logTrace(listIndex.parent(), Common::LOG_MAILBOX_SYNC, QStringLiteral("TreeItemMailbox::handleVanished"), str);
656                     continue;
657                 }
658             } else {
659                 // Again, VANISHED can refer to non-existing UIDs
660                 QString str;
661                 QTextStream ss(&str);
662                 ss << "VANISHED refers to UID " << uid << " which is too low (lowest UID is " <<
663                       static_cast<TreeItemMessage*>(list->m_children.front())->uid() << ")";
664                 ss.flush();
665                 qDebug() << str.toUtf8().constData();
666                 model->logTrace(listIndex.parent(), Common::LOG_MAILBOX_SYNC, QStringLiteral("TreeItemMailbox::handleVanished"), str);
667                 continue;
668             }
669         }
670 
671         int row = msgCandidate->row();
672         Q_ASSERT(row == it - list->m_children.begin());
673         model->beginRemoveRows(listIndex, row, row);
674         it = list->m_children.erase(it);
675         for (auto furtherMessage = it; furtherMessage != list->m_children.end(); ++furtherMessage) {
676             --static_cast<TreeItemMessage *>(*furtherMessage)->m_offset;
677         }
678         model->endRemoveRows();
679 
680         if (syncState.uidNext() <= uid) {
681             // We're informed about a message being deleted; this means that that UID must have been in the mailbox for some
682             // (possibly tiny) time and we can therefore use it to get an idea about the UIDNEXT
683             syncState.setUidNext(uid + 1);
684         }
685         model->cache()->clearMessage(mailbox(), uid);
686         delete msgCandidate;
687     }
688 
689     if (resp.earlier == Responses::Vanished::EARLIER && static_cast<uint>(list->m_children.size()) < syncState.exists()) {
690         // Okay, there were some new arrivals which we failed to take into account because we had processed EXISTS
691         // before VANISHED (EARLIER). That means that we have to add some of that messages back right now.
692         int newArrivals = syncState.exists() - list->m_children.size();
693         Q_ASSERT(newArrivals > 0);
694         QModelIndex parent = list->toIndex(model);
695         int offset = list->m_children.size();
696         model->beginInsertRows(parent, offset, syncState.exists() - 1);
697         for (int i = 0; i < newArrivals; ++i) {
698             TreeItemMessage *msg = new TreeItemMessage(list);
699             msg->m_offset = i + offset;
700             list->m_children << msg;
701             // yes, we really have to add this message with UID 0 :(
702         }
703         model->endInsertRows();
704     }
705 
706     list->m_totalMessageCount = list->m_children.size();
707     syncState.setExists(list->m_totalMessageCount);
708     list->recalcVariousMessageCounts(const_cast<Model *>(model));
709 
710     if (list->accessFetchStatus() == DONE) {
711         // Previously, we were synced, so we got to save this update
712         saveSyncStateAndUids(model);
713     }
714 }
715 
716 /** @short Process the EXISTS response
717 
718 This function assumes that the mailbox is already synced.
719 */
handleExists(Model * const model,const Responses::NumberResponse & resp)720 void TreeItemMailbox::handleExists(Model *const model, const Responses::NumberResponse &resp)
721 {
722     Q_ASSERT(resp.kind == Responses::EXISTS);
723     TreeItemMsgList *list = dynamic_cast<TreeItemMsgList *>(m_children[0]);
724     Q_ASSERT(list);
725     // This is a bit tricky -- unfortunately, we can't assume anything about the UID of new arrivals. On the other hand,
726     // these messages can be referenced by (even unrequested) FETCH responses and deleted by EXPUNGE, so we really want
727     // to add them to the tree.
728     int newArrivals = resp.number - list->m_children.size();
729     if (newArrivals < 0) {
730         throw UnexpectedResponseReceived("EXISTS response attempted to decrease number of messages", resp);
731     }
732     syncState.setExists(resp.number);
733     if (newArrivals == 0) {
734         // remains unchanged...
735         return;
736     }
737 
738     QModelIndex parent = list->toIndex(model);
739     int offset = list->m_children.size();
740     model->beginInsertRows(parent, offset, resp.number - 1);
741     for (int i = 0; i < newArrivals; ++i) {
742         TreeItemMessage *msg = new TreeItemMessage(list);
743         msg->m_offset = i + offset;
744         list->m_children << msg;
745         // yes, we really have to add this message with UID 0 :(
746     }
747     model->endInsertRows();
748     list->m_totalMessageCount = resp.number;
749     list->setFetchStatus(LOADING);
750     model->emitMessageCountChanged(this);
751 }
752 
partIdToPtr(Model * const model,TreeItemMessage * message,const QByteArray & msgId)753 TreeItemPart *TreeItemMailbox::partIdToPtr(Model *const model, TreeItemMessage *message, const QByteArray &msgId)
754 {
755     QByteArray partIdentification;
756     if (msgId.startsWith("BODY[")) {
757         partIdentification = msgId.mid(5, msgId.size() - 6);
758     } else if (msgId.startsWith("BODY.PEEK[")) {
759         partIdentification = msgId.mid(10, msgId.size() - 11);
760     } else if (msgId.startsWith("BINARY.PEEK[")) {
761         partIdentification = msgId.mid(12, msgId.size() - 13);
762     } else if (msgId.startsWith("BINARY[")) {
763         partIdentification = msgId.mid(7, msgId.size() - 8);
764     } else {
765         throw UnknownMessageIndex(QByteArray("Fetch identifier doesn't start with reasonable prefix: " + msgId).constData());
766     }
767 
768     TreeItem *item = message;
769     Q_ASSERT(item);
770     QList<QByteArray> separated = partIdentification.split('.');
771     for (QList<QByteArray>::const_iterator it = separated.constBegin(); it != separated.constEnd(); ++it) {
772         bool ok;
773         uint number = it->toUInt(&ok);
774         if (!ok) {
775             // It isn't a number, so let's check for that special modifiers
776             if (it + 1 != separated.constEnd()) {
777                 // If it isn't at the very end, it's an error
778                 throw UnknownMessageIndex(QByteArray("Part offset contains non-numeric identifiers in the middle: " + msgId).constData());
779             }
780             // Recognize the valid modifiers
781             if (*it == "HEADER")
782                 item = item->specialColumnPtr(0, OFFSET_HEADER);
783             else if (*it == "TEXT")
784                 item = item->specialColumnPtr(0, OFFSET_TEXT);
785             else if (*it == "MIME")
786                 item = item->specialColumnPtr(0, OFFSET_MIME);
787             else
788                 throw UnknownMessageIndex(QByteArray("Can't translate received offset of the message part to a number: " + msgId).constData());
789             break;
790         }
791 
792         // Normal path: descending down and finding the correct part
793         TreeItemPart *part = dynamic_cast<TreeItemPart *>(item->child(0, model));
794         if (part && part->isTopLevelMultiPart())
795             item = part;
796         item = item->child(number - 1, model);
797         if (! item) {
798             throw UnknownMessageIndex(QStringLiteral(
799                                           "Offset of the message part not found: message %1 (UID %2), current number %3, full identification %4")
800                                       .arg(QString::number(message->row()), QString::number(message->uid()),
801                                            QString::number(number), QString::fromUtf8(msgId)).toUtf8().constData());
802         }
803     }
804     TreeItemPart *part = dynamic_cast<TreeItemPart *>(item);
805     return part;
806 }
807 
isSelectable() const808 bool TreeItemMailbox::isSelectable() const
809 {
810     return !m_metadata.flags.contains(QStringLiteral("\\NOSELECT")) && !m_metadata.flags.contains(QStringLiteral("\\NONEXISTENT"));
811 }
812 
813 
814 
TreeItemMsgList(TreeItem * parent)815 TreeItemMsgList::TreeItemMsgList(TreeItem *parent):
816     TreeItem(parent), m_numberFetchingStatus(NONE), m_totalMessageCount(-1),
817     m_unreadMessageCount(-1), m_recentMessageCount(-1)
818 {
819     if (!parent->parent())
820         setFetchStatus(DONE);
821 }
822 
fetch(Model * const model)823 void TreeItemMsgList::fetch(Model *const model)
824 {
825     if (fetched() || isUnavailable())
826         return;
827 
828     if (!loading()) {
829         setFetchStatus(LOADING);
830         // We can't ask right now, has to wait till the end of the event loop
831         CALL_LATER(model, askForMessagesInMailbox, Q_ARG(QModelIndex, toIndex(model)));
832     }
833 }
834 
fetchNumbers(Model * const model)835 void TreeItemMsgList::fetchNumbers(Model *const model)
836 {
837     if (m_numberFetchingStatus == NONE) {
838         m_numberFetchingStatus = LOADING;
839         model->askForNumberOfMessages(this);
840     }
841 }
842 
rowCount(Model * const model)843 unsigned int TreeItemMsgList::rowCount(Model *const model)
844 {
845     return childrenCount(model);
846 }
847 
data(Model * const model,int role)848 QVariant TreeItemMsgList::data(Model *const model, int role)
849 {
850     if (role == RoleIsFetched)
851         return fetched();
852     if (role == RoleIsUnavailable)
853         return isUnavailable();
854 
855     if (role != Qt::DisplayRole)
856         return QVariant();
857 
858     if (!parent())
859         return QVariant();
860 
861     if (loading())
862         return QLatin1String("[loading messages...]");
863 
864     if (isUnavailable())
865         return QLatin1String("[offline]");
866 
867     if (fetched())
868         return hasChildren(model) ? QStringLiteral("[%1 messages]").arg(childrenCount(model)) : QStringLiteral("[no messages]");
869 
870     return QLatin1String("[messages?]");
871 }
872 
hasChildren(Model * const model)873 bool TreeItemMsgList::hasChildren(Model *const model)
874 {
875     Q_UNUSED(model);
876     return true; // we can easily wait here
877 }
878 
totalMessageCount(Model * const model)879 int TreeItemMsgList::totalMessageCount(Model *const model)
880 {
881     // Yes, the numbers can be accommodated by a full mailbox sync, but that's not really what we shall do from this context.
882     // Because we want to allow the old-school polling for message numbers, we have to look just at the numberFetched() state.
883     if (!numbersFetched())
884         fetchNumbers(model);
885     return m_totalMessageCount;
886 }
887 
unreadMessageCount(Model * const model)888 int TreeItemMsgList::unreadMessageCount(Model *const model)
889 {
890     // See totalMessageCount()
891     if (!numbersFetched())
892         fetchNumbers(model);
893     return m_unreadMessageCount;
894 }
895 
recentMessageCount(Model * const model)896 int TreeItemMsgList::recentMessageCount(Model *const model)
897 {
898     // See totalMessageCount()
899     if (!numbersFetched())
900         fetchNumbers(model);
901     return m_recentMessageCount;
902 }
903 
recalcVariousMessageCounts(Model * model)904 void TreeItemMsgList::recalcVariousMessageCounts(Model *model)
905 {
906     m_unreadMessageCount = 0;
907     m_recentMessageCount = 0;
908     for (int i = 0; i < m_children.size(); ++i) {
909         TreeItemMessage *message = static_cast<TreeItemMessage *>(m_children[i]);
910         bool isRead, isRecent;
911         message->checkFlagsReadRecent(isRead, isRecent);
912         if (!message->m_flagsHandled)
913             message->m_wasUnread = ! isRead;
914         message->m_flagsHandled = true;
915         if (!isRead)
916             ++m_unreadMessageCount;
917         if (isRecent)
918             ++m_recentMessageCount;
919     }
920     m_totalMessageCount = m_children.size();
921     m_numberFetchingStatus = DONE;
922     model->emitMessageCountChanged(static_cast<TreeItemMailbox *>(parent()));
923 }
924 
recalcVariousMessageCountsOnExpunge(Model * model,TreeItemMessage * expungedMessage)925 void TreeItemMsgList::recalcVariousMessageCountsOnExpunge(Model *model, TreeItemMessage *expungedMessage)
926 {
927     if (m_numberFetchingStatus != DONE) {
928         // In case the counts weren't synced before, we cannot really rely on them now -> go to the slow path
929         recalcVariousMessageCounts(model);
930         return;
931     }
932 
933     bool isRead, isRecent;
934     expungedMessage->checkFlagsReadRecent(isRead, isRecent);
935     if (expungedMessage->m_flagsHandled) {
936         if (!isRead)
937             --m_unreadMessageCount;
938         if (isRecent)
939             --m_recentMessageCount;
940     }
941     model->emitMessageCountChanged(static_cast<TreeItemMailbox *>(parent()));
942 }
943 
resetWasUnreadState()944 void TreeItemMsgList::resetWasUnreadState()
945 {
946     for (int i = 0; i < m_children.size(); ++i) {
947         TreeItemMessage *message = static_cast<TreeItemMessage *>(m_children[i]);
948         message->m_wasUnread = ! message->isMarkedAsRead();
949     }
950 }
951 
numbersFetched() const952 bool TreeItemMsgList::numbersFetched() const
953 {
954     return m_numberFetchingStatus == DONE;
955 }
956 
957 
958 
MessageDataPayload()959 MessageDataPayload::MessageDataPayload()
960     : m_size(0)
961     , m_hdrListPostNo(false)
962     , m_partHeader(nullptr)
963     , m_partText(nullptr)
964     , m_gotEnvelope(false)
965     , m_gotInternalDate(false)
966     , m_gotSize(false)
967     , m_gotBodystructure(false)
968     , m_gotHdrReferences(false)
969     , m_gotHdrListPost(false)
970 {
971 }
972 
isComplete() const973 bool MessageDataPayload::isComplete() const
974 {
975     return m_gotEnvelope && m_gotInternalDate && m_gotSize && m_gotBodystructure;
976 }
977 
gotEnvelope() const978 bool MessageDataPayload::gotEnvelope() const
979 {
980     return m_gotEnvelope;
981 }
982 
gotInternalDate() const983 bool MessageDataPayload::gotInternalDate() const
984 {
985     return m_gotInternalDate;
986 }
987 
gotSize() const988 bool MessageDataPayload::gotSize() const
989 {
990     return m_gotSize;
991 }
992 
gotHdrReferences() const993 bool MessageDataPayload::gotHdrReferences() const
994 {
995     return m_gotHdrReferences;
996 }
997 
gotHdrListPost() const998 bool MessageDataPayload::gotHdrListPost() const
999 {
1000     return m_gotHdrListPost;
1001 }
1002 
envelope() const1003 const Message::Envelope &MessageDataPayload::envelope() const
1004 {
1005     return m_envelope;
1006 }
1007 
setEnvelope(const Message::Envelope & envelope)1008 void MessageDataPayload::setEnvelope(const Message::Envelope &envelope)
1009 {
1010     m_envelope = envelope;
1011     m_gotEnvelope = true;
1012 }
1013 
internalDate() const1014 const QDateTime &MessageDataPayload::internalDate() const
1015 {
1016     return m_internalDate;
1017 }
1018 
setInternalDate(const QDateTime & internalDate)1019 void MessageDataPayload::setInternalDate(const QDateTime &internalDate)
1020 {
1021     m_internalDate = internalDate;
1022     m_gotInternalDate = true;
1023 }
1024 
size() const1025 quint64 MessageDataPayload::size() const
1026 {
1027     return m_size;
1028 }
1029 
setSize(quint64 size)1030 void MessageDataPayload::setSize(quint64 size)
1031 {
1032     m_size = size;
1033     m_gotSize = true;
1034 }
1035 
hdrReferences() const1036 const QList<QByteArray> &MessageDataPayload::hdrReferences() const
1037 {
1038     return m_hdrReferences;
1039 }
1040 
setHdrReferences(const QList<QByteArray> & hdrReferences)1041 void MessageDataPayload::setHdrReferences(const QList<QByteArray> &hdrReferences)
1042 {
1043     m_hdrReferences = hdrReferences;
1044     m_gotHdrReferences = true;
1045 }
1046 
hdrListPost() const1047 const QList<QUrl> &MessageDataPayload::hdrListPost() const
1048 {
1049     return m_hdrListPost;
1050 }
1051 
hdrListPostNo() const1052 bool MessageDataPayload::hdrListPostNo() const
1053 {
1054     return m_hdrListPostNo;
1055 }
1056 
setHdrListPost(const QList<QUrl> & hdrListPost)1057 void MessageDataPayload::setHdrListPost(const QList<QUrl> &hdrListPost)
1058 {
1059     m_hdrListPost = hdrListPost;
1060     m_gotHdrListPost = true;
1061 }
1062 
setHdrListPostNo(const bool hdrListPostNo)1063 void MessageDataPayload::setHdrListPostNo(const bool hdrListPostNo)
1064 {
1065     m_hdrListPostNo = hdrListPostNo;
1066     m_gotHdrListPost = true;
1067 }
1068 
rememberedBodyStructure() const1069 const QByteArray &MessageDataPayload::rememberedBodyStructure() const
1070 {
1071     return m_rememberedBodyStructure;
1072 }
1073 
setRememberedBodyStructure(const QByteArray & blob)1074 void MessageDataPayload::setRememberedBodyStructure(const QByteArray &blob)
1075 {
1076     m_rememberedBodyStructure = blob;
1077     m_gotBodystructure = true;
1078 }
1079 
gotRemeberedBodyStructure() const1080 bool MessageDataPayload::gotRemeberedBodyStructure() const
1081 {
1082     return m_gotBodystructure;
1083 }
1084 
partHeader() const1085 TreeItemPart *MessageDataPayload::partHeader() const
1086 {
1087     return m_partHeader.get();
1088 }
1089 
setPartHeader(std::unique_ptr<TreeItemPart> part)1090 void MessageDataPayload::setPartHeader(std::unique_ptr<TreeItemPart> part)
1091 {
1092     m_partHeader = std::move(part);
1093 }
1094 
partText() const1095 TreeItemPart *MessageDataPayload::partText() const
1096 {
1097     return m_partText.get();
1098 }
1099 
setPartText(std::unique_ptr<TreeItemPart> part)1100 void MessageDataPayload::setPartText(std::unique_ptr<TreeItemPart> part)
1101 {
1102     m_partText = std::move(part);
1103 }
1104 
1105 
TreeItemMessage(TreeItem * parent)1106 TreeItemMessage::TreeItemMessage(TreeItem *parent):
1107     TreeItem(parent), m_offset(-1), m_uid(0), m_data(0), m_flagsHandled(false), m_wasUnread(false)
1108 {
1109 }
1110 
~TreeItemMessage()1111 TreeItemMessage::~TreeItemMessage()
1112 {
1113     delete m_data;
1114 }
1115 
fetch(Model * const model)1116 void TreeItemMessage::fetch(Model *const model)
1117 {
1118     if (fetched() || loading() || isUnavailable())
1119         return;
1120 
1121     if (m_uid) {
1122         // Message UID is already known, which means that we can request data for this message
1123         model->askForMsgMetadata(this, Model::PRELOAD_PER_POLICY);
1124     } else {
1125         // The UID is not known yet, so we can't initiate a UID FETCH at this point. However, we mark
1126         // this message as "loading", which has the side effect that it will get re-fetched as soon as
1127         // the UID arrives -- see TreeItemMailbox::handleFetchResponse(), the section which deals with
1128         // setting previously unknown UIDs, and the similar code in ObtainSynchronizedMailboxTask.
1129         //
1130         // Even though this breaks the message preload done in Model::_askForMsgmetadata, chances are that
1131         // the UIDs will arrive rather soon for all of the pending messages, and the request for metadata
1132         // will therefore get queued roughly at the same time.  This gives the KeepMailboxOpenTask a chance
1133         // to group similar requests together.  To reiterate:
1134         // - Messages are attempted to get *preloaded* (ie. requesting metadata even for messages that are not
1135         //   yet shown) as usual; this could fail because the UIDs might not be known yet.
1136         // - The actual FETCH could be batched by the KeepMailboxOpenTask anyway
1137         // - Hence, this should be still pretty fast and efficient
1138         setFetchStatus(LOADING);
1139     }
1140 }
1141 
rowCount(Model * const model)1142 unsigned int TreeItemMessage::rowCount(Model *const model)
1143 {
1144     if (!data()->gotRemeberedBodyStructure()) {
1145         fetch(model);
1146     }
1147     return m_children.size();
1148 }
1149 
setChildren(const TreeItemChildrenList & items)1150 TreeItemChildrenList TreeItemMessage::setChildren(const TreeItemChildrenList &items)
1151 {
1152     auto origStatus = accessFetchStatus();
1153     auto res = TreeItem::setChildren(items);
1154     setFetchStatus(origStatus);
1155     return res;
1156 }
1157 
columnCount()1158 unsigned int TreeItemMessage::columnCount()
1159 {
1160     static_assert(OFFSET_HEADER < OFFSET_TEXT && OFFSET_MIME == OFFSET_TEXT + 1,
1161                   "We need column 0 for regular children and columns 1 and 2 for OFFSET_HEADER and OFFSET_TEXT.");
1162     // Oh, and std::max is not constexpr in C++11.
1163     return OFFSET_MIME;
1164 }
1165 
specialColumnPtr(int row,int column) const1166 TreeItem *TreeItemMessage::specialColumnPtr(int row, int column) const
1167 {
1168     // This is a nasty one -- we have an irregular shape...
1169 
1170     // No extra columns on other rows
1171     if (row != 0)
1172         return 0;
1173 
1174     switch (column) {
1175     case OFFSET_TEXT:
1176         if (!data()->partText()) {
1177             data()->setPartText(std::unique_ptr<TreeItemPart>(new TreeItemModifiedPart(const_cast<TreeItemMessage *>(this), OFFSET_TEXT)));
1178         }
1179         return data()->partText();
1180     case OFFSET_HEADER:
1181         if (!data()->partHeader()) {
1182             data()->setPartHeader(std::unique_ptr<TreeItemPart>(new TreeItemModifiedPart(const_cast<TreeItemMessage *>(this), OFFSET_HEADER)));
1183         }
1184         return data()->partHeader();
1185     default:
1186         return 0;
1187     }
1188 }
1189 
row() const1190 int TreeItemMessage::row() const
1191 {
1192     Q_ASSERT(m_offset != -1);
1193     return m_offset;
1194 }
1195 
data(Model * const model,int role)1196 QVariant TreeItemMessage::data(Model *const model, int role)
1197 {
1198     if (!parent())
1199         return QVariant();
1200 
1201     // Special item roles which should not trigger fetching of message metadata
1202     switch (role) {
1203     case RoleMessageUid:
1204         return m_uid ? QVariant(m_uid) : QVariant();
1205     case RoleIsFetched:
1206         return fetched();
1207     case RoleIsUnavailable:
1208         return isUnavailable();
1209     case RoleMessageFlags:
1210         // The flags are already sorted by Model::normalizeFlags()
1211         return m_flags;
1212     case RoleMessageIsMarkedDeleted:
1213         return isMarkedAsDeleted();
1214     case RoleMessageIsMarkedRead:
1215         return isMarkedAsRead();
1216     case RoleMessageIsMarkedForwarded:
1217         return isMarkedAsForwarded();
1218     case RoleMessageIsMarkedReplied:
1219         return isMarkedAsReplied();
1220     case RoleMessageIsMarkedRecent:
1221         return isMarkedAsRecent();
1222     case RoleMessageIsMarkedFlagged:
1223         return isMarkedAsFlagged();
1224     case RoleMessageIsMarkedJunk:
1225         return isMarkedAsJunk();
1226     case RoleMessageIsMarkedNotJunk:
1227         return isMarkedAsNotJunk();
1228     case RoleMessageFuzzyDate:
1229     {
1230         // When the QML ListView is configured with its section.* properties, it will call the corresponding data() section *very*
1231         // often.  The data are however only "needed" when the real items are visible, and when they are visible, the data() will
1232         // get called anyway and the correct stuff will ultimately arrive.  This is why we don't call fetch() from here.
1233         //
1234         // FIXME: double-check the above once again! Maybe it was just a side effect of too fast updates of the currentIndex?
1235         if (!fetched()) {
1236             return QVariant();
1237         }
1238 
1239         QDateTime timestamp = envelope(model).date;
1240         if (!timestamp.isValid())
1241             return QString();
1242 
1243         if (timestamp.date() == QDate::currentDate())
1244             return Model::tr("Today");
1245 
1246         int beforeDays = timestamp.date().daysTo(QDate::currentDate());
1247         if (beforeDays >= 0 && beforeDays < 7)
1248             return Model::tr("Last Week");
1249 
1250         return QDate(timestamp.date().year(), timestamp.date().month(), 1).toString(
1251             Model::tr("MMMM yyyy", "The format specifiers (yyyy, etc) must not be translated, "
1252                       "but their order can be changed to follow the local conventions. "
1253                       "For valid specifiers see http://doc.qt.io/qt-5/qdate.html#toString"));
1254     }
1255     case RoleMessageWasUnread:
1256         return m_wasUnread;
1257     case RoleThreadRootWithUnreadMessages:
1258         // This one doesn't really make much sense here, but we do want to catch it to prevent a fetch request from this context
1259         qDebug() << "Warning: asked for RoleThreadRootWithUnreadMessages on TreeItemMessage. This does not make sense.";
1260         return QVariant();
1261     case RoleMailboxName:
1262     case RoleMailboxUidValidity:
1263         return parent()->parent()->data(model, role);
1264     case RolePartMimeType:
1265         return QByteArrayLiteral("message/rfc822");
1266     }
1267 
1268     // Any other roles will result in fetching the data; however, we won't exit if the data isn't available yet
1269     fetch(model);
1270 
1271     switch (role) {
1272     case Qt::DisplayRole:
1273         if (loading()) {
1274             return QStringLiteral("[loading UID %1...]").arg(QString::number(uid()));
1275         } else if (isUnavailable()) {
1276             return QStringLiteral("[offline UID %1]").arg(QString::number(uid()));
1277         } else {
1278             return QStringLiteral("UID %1: %2").arg(QString::number(uid()), data()->envelope().subject);
1279         }
1280     case Qt::ToolTipRole:
1281         if (fetched()) {
1282             QString buf;
1283             QTextStream stream(&buf);
1284             stream << data()->envelope();
1285             return UiUtils::Formatting::htmlEscaped(buf);
1286         } else {
1287             return QVariant();
1288         }
1289     case RoleMessageSize:
1290         return data()->gotSize() ? QVariant::fromValue(data()->size()) : QVariant();
1291     case RoleMessageInternalDate:
1292         return data()->gotInternalDate() ? data()->internalDate() : QVariant();
1293     case RoleMessageHeaderReferences:
1294         return data()->gotHdrReferences() ? QVariant::fromValue(data()->hdrReferences()) : QVariant();
1295     case RoleMessageHeaderListPost:
1296         if (data()->gotHdrListPost()) {
1297             QVariantList res;
1298             Q_FOREACH(const QUrl &url, data()->hdrListPost())
1299                 res << url;
1300             return res;
1301         } else {
1302             return QVariant();
1303         }
1304     case RoleMessageHeaderListPostNo:
1305         return data()->gotHdrListPost() ? QVariant(data()->hdrListPostNo()) : QVariant();
1306     }
1307 
1308     if (data()->gotEnvelope()) {
1309         // If the envelope is already available, we might be able to deliver some bits even prior to full fetch being done.
1310         //
1311         // Only those values which need ENVELOPE go here!
1312         switch (role) {
1313         case RoleMessageSubject:
1314             return data()->gotEnvelope() ? QVariant(data()->envelope().subject) : QVariant();
1315         case RoleMessageDate:
1316             return data()->gotEnvelope() ? envelope(model).date : QVariant();
1317         case RoleMessageFrom:
1318             return addresListToQVariant(envelope(model).from);
1319         case RoleMessageTo:
1320             return addresListToQVariant(envelope(model).to);
1321         case RoleMessageCc:
1322             return addresListToQVariant(envelope(model).cc);
1323         case RoleMessageBcc:
1324             return addresListToQVariant(envelope(model).bcc);
1325         case RoleMessageSender:
1326             return addresListToQVariant(envelope(model).sender);
1327         case RoleMessageReplyTo:
1328             return addresListToQVariant(envelope(model).replyTo);
1329         case RoleMessageInReplyTo:
1330             return QVariant::fromValue(envelope(model).inReplyTo);
1331         case RoleMessageMessageId:
1332             return envelope(model).messageId;
1333         case RoleMessageEnvelope:
1334             return QVariant::fromValue<Message::Envelope>(envelope(model));
1335         case RoleMessageHasAttachments:
1336             return hasAttachments(model);
1337         }
1338     }
1339 
1340     return QVariant();
1341 }
1342 
addresListToQVariant(const QList<Imap::Message::MailAddress> & addressList)1343 QVariantList TreeItemMessage::addresListToQVariant(const QList<Imap::Message::MailAddress> &addressList)
1344 {
1345     QVariantList res;
1346     foreach(const Imap::Message::MailAddress& address, addressList) {
1347         res.append(QVariant(QStringList() << address.name << address.adl << address.mailbox << address.host));
1348     }
1349     return res;
1350 }
1351 
1352 
1353 namespace {
1354 
1355 /** @short Find a string based on d-ptr equality
1356 
1357 This works because our flags always use implicit sharing. If they didn't use that, this method wouldn't work.
1358 */
containsStringByDPtr(const QStringList & haystack,const QString & needle)1359 bool containsStringByDPtr(const QStringList &haystack, const QString &needle)
1360 {
1361     const auto sentinel = const_cast<QString&>(needle).data_ptr();
1362     Q_FOREACH(const auto &item, haystack) {
1363         if (const_cast<QString&>(item).data_ptr() == sentinel)
1364             return true;
1365     }
1366     return false;
1367 }
1368 
1369 }
1370 
isMarkedAsDeleted() const1371 bool TreeItemMessage::isMarkedAsDeleted() const
1372 {
1373     return containsStringByDPtr(m_flags, FlagNames::deleted);
1374 }
1375 
isMarkedAsRead() const1376 bool TreeItemMessage::isMarkedAsRead() const
1377 {
1378     return containsStringByDPtr(m_flags, FlagNames::seen);
1379 }
1380 
isMarkedAsReplied() const1381 bool TreeItemMessage::isMarkedAsReplied() const
1382 {
1383     return containsStringByDPtr(m_flags, FlagNames::answered);
1384 }
1385 
isMarkedAsForwarded() const1386 bool TreeItemMessage::isMarkedAsForwarded() const
1387 {
1388     return containsStringByDPtr(m_flags, FlagNames::forwarded);
1389 }
1390 
isMarkedAsRecent() const1391 bool TreeItemMessage::isMarkedAsRecent() const
1392 {
1393     return containsStringByDPtr(m_flags, FlagNames::recent);
1394 }
1395 
isMarkedAsFlagged() const1396 bool TreeItemMessage::isMarkedAsFlagged() const
1397 {
1398     return containsStringByDPtr(m_flags, FlagNames::flagged);
1399 }
1400 
isMarkedAsJunk() const1401 bool TreeItemMessage::isMarkedAsJunk() const
1402 {
1403     return containsStringByDPtr(m_flags, FlagNames::junk);
1404 }
1405 
isMarkedAsNotJunk() const1406 bool TreeItemMessage::isMarkedAsNotJunk() const
1407 {
1408     return containsStringByDPtr(m_flags, FlagNames::notjunk);
1409 }
1410 
checkFlagsReadRecent(bool & isRead,bool & isRecent) const1411 void TreeItemMessage::checkFlagsReadRecent(bool &isRead, bool &isRecent) const
1412 {
1413     const auto dRead = const_cast<QString&>(FlagNames::seen).data_ptr();
1414     const auto dRecent = const_cast<QString&>(FlagNames::recent).data_ptr();
1415     auto end = m_flags.end();
1416     auto it = m_flags.begin();
1417     isRead = isRecent = false;
1418     while (it != end && !(isRead && isRecent)) {
1419         isRead |= const_cast<QString&>(*it).data_ptr() == dRead;
1420         isRecent |= const_cast<QString&>(*it).data_ptr() == dRecent;
1421         ++it;
1422     }
1423 }
1424 
uid() const1425 uint TreeItemMessage::uid() const
1426 {
1427     return m_uid;
1428 }
1429 
envelope(Model * const model)1430 Message::Envelope TreeItemMessage::envelope(Model *const model)
1431 {
1432     fetch(model);
1433     return data()->envelope();
1434 }
1435 
internalDate(Model * const model)1436 QDateTime TreeItemMessage::internalDate(Model *const model)
1437 {
1438     fetch(model);
1439     return data()->internalDate();
1440 }
1441 
size(Model * const model)1442 quint64 TreeItemMessage::size(Model *const model)
1443 {
1444     fetch(model);
1445     return data()->size();
1446 }
1447 
setFlags(TreeItemMsgList * list,const QStringList & flags)1448 void TreeItemMessage::setFlags(TreeItemMsgList *list, const QStringList &flags)
1449 {
1450     // wasSeen is used to determine if the message was marked as read before this operation
1451     bool wasSeen = isMarkedAsRead();
1452     m_flags = flags;
1453     if (list->m_numberFetchingStatus == DONE) {
1454         bool isSeen = isMarkedAsRead();
1455         if (m_flagsHandled) {
1456             if (wasSeen && !isSeen) {
1457                 ++list->m_unreadMessageCount;
1458                 // leave the message as "was unread" so it persists in the view when read messages are hidden
1459                 m_wasUnread = true;
1460             } else if (!wasSeen && isSeen) {
1461                 --list->m_unreadMessageCount;
1462             }
1463         } else {
1464             // it's a new message
1465             m_flagsHandled = true;
1466             if (!isSeen) {
1467                 ++list->m_unreadMessageCount;
1468                 // mark the message as "was unread" so it shows up in the view when read messages are hidden
1469                 m_wasUnread = true;
1470             }
1471         }
1472     }
1473 }
1474 
1475 /** @short Process the data found in the headers passed along and file in auxiliary metadata
1476 
1477 This function accepts a snippet containing some RFC5322 headers of a message, no matter what headers are actually
1478 present in the passed text.  The headers are parsed and those recognized are used as a source of data to file
1479 the "auxiliary metadata" of this TreeItemMessage (basically anything not available in ENVELOPE, UID, FLAGS,
1480 INTERNALDATE etc).
1481 */
processAdditionalHeaders(Model * model,const QByteArray & rawHeaders)1482 void TreeItemMessage::processAdditionalHeaders(Model *model, const QByteArray &rawHeaders)
1483 {
1484     Imap::LowLevelParser::Rfc5322HeaderParser parser;
1485     bool ok = parser.parse(rawHeaders);
1486     if (!ok) {
1487         model->logTrace(0, Common::LOG_OTHER, QStringLiteral("Rfc5322HeaderParser"),
1488                         QStringLiteral("Unspecified error during RFC5322 header parsing"));
1489     }
1490 
1491     data()->setHdrReferences(parser.references);
1492     QList<QUrl> hdrListPost;
1493     if (!parser.listPost.isEmpty()) {
1494         Q_FOREACH(const QByteArray &item, parser.listPost)
1495             hdrListPost << QUrl(QString::fromUtf8(item));
1496         data()->setHdrListPost(hdrListPost);
1497     }
1498     // That's right, this can only be set, not ever reset from this context.
1499     // This is because we absolutely want to support incremental header arrival.
1500     if (parser.listPostNo)
1501         data()->setHdrListPostNo(true);
1502 }
1503 
hasAttachments(Model * const model)1504 bool TreeItemMessage::hasAttachments(Model *const model)
1505 {
1506     fetch(model);
1507 
1508     if (!fetched())
1509         return false;
1510 
1511     if (m_children.isEmpty()) {
1512         // strange, but why not, I guess
1513         return false;
1514     } else if (m_children.size() > 1) {
1515         // Again, very strange -- the message should have had a single multipart as a root node, but let's cope with this as well
1516         return true;
1517     } else {
1518         return hasNestedAttachments(model, static_cast<TreeItemPart*>(m_children[0]));
1519     }
1520 }
1521 
1522 /** @short Walk the MIME tree starting at @arg part and check if there are any attachments below (or at there) */
hasNestedAttachments(Model * const model,TreeItemPart * part)1523 bool TreeItemMessage::hasNestedAttachments(Model *const model, TreeItemPart *part)
1524 {
1525     while (true) {
1526 
1527         const QByteArray mimeType = part->mimeType();
1528 
1529         if (mimeType == "multipart/signed" && part->childrenCount(model) == 2) {
1530             // "strip" the signature, look at what's inside
1531             part = static_cast<TreeItemPart*>(part->child(0, model));
1532         } else if (mimeType.startsWith("multipart/")) {
1533             // Return false iff no children is/has an attachment.
1534             // Originally this code was like this only for multipart/alternative, but in the end Stephan Platz lobbied for
1535             // treating ML signatures the same (which means multipart/mixed) has to be included, and there's also a RFC
1536             // which says that unrecognized multiparts should be treated exactly like a multipart/mixed.
1537             // As a bonus, this makes it possible to get rid of an extra branch for single-childed multiparts.
1538             for (uint i = 0; i < part->childrenCount(model); ++i) {
1539                 if (hasNestedAttachments(model, static_cast<TreeItemPart*>(part->child(i, model)))) {
1540                     return true;
1541                 }
1542             }
1543             return false;
1544         } else if (mimeType == "text/html" || mimeType == "text/plain") {
1545             // See AttachmentView for details behind this.
1546             const QByteArray contentDisposition = part->bodyDisposition().toLower();
1547             const bool isInline = contentDisposition.isEmpty() || contentDisposition == "inline";
1548             const bool looksLikeAttachment = !part->fileName().isEmpty();
1549 
1550             return looksLikeAttachment || !isInline;
1551         } else {
1552             // anything else must surely be an attachment
1553             return true;
1554         }
1555     }
1556 }
1557 
1558 
TreeItemPart(TreeItem * parent,const QByteArray & mimeType)1559 TreeItemPart::TreeItemPart(TreeItem *parent, const QByteArray &mimeType):
1560     TreeItem(parent), m_mimeType(mimeType.toLower()), m_octets(0), m_partMime(0), m_partRaw(0)
1561 {
1562     if (isTopLevelMultiPart()) {
1563         // Note that top-level multipart messages are special, their immediate contents
1564         // can't be fetched. That's why we have to update the status here.
1565         setFetchStatus(DONE);
1566     }
1567 }
1568 
TreeItemPart(TreeItem * parent)1569 TreeItemPart::TreeItemPart(TreeItem *parent):
1570     TreeItem(parent), m_mimeType("text/plain"), m_octets(0), m_partMime(0), m_partRaw(0)
1571 {
1572 }
1573 
~TreeItemPart()1574 TreeItemPart::~TreeItemPart()
1575 {
1576     delete m_partMime;
1577     delete m_partRaw;
1578 }
1579 
childrenCount(Model * const model)1580 unsigned int TreeItemPart::childrenCount(Model *const model)
1581 {
1582     Q_UNUSED(model);
1583     return m_children.size();
1584 }
1585 
child(const int offset,Model * const model)1586 TreeItem *TreeItemPart::child(const int offset, Model *const model)
1587 {
1588     Q_UNUSED(model);
1589     if (offset >= 0 && offset < m_children.size())
1590         return m_children[ offset ];
1591     else
1592         return 0;
1593 }
1594 
setChildren(const TreeItemChildrenList & items)1595 TreeItemChildrenList TreeItemPart::setChildren(const TreeItemChildrenList &items)
1596 {
1597     FetchingState fetchStatus = accessFetchStatus();
1598     auto res = TreeItem::setChildren(items);
1599     setFetchStatus(fetchStatus);
1600     return res;
1601 }
1602 
fetch(Model * const model)1603 void TreeItemPart::fetch(Model *const model)
1604 {
1605     if (fetched() || loading() || isUnavailable())
1606         return;
1607 
1608     setFetchStatus(LOADING);
1609     model->askForMsgPart(this);
1610 }
1611 
fetchFromCache(Model * const model)1612 void TreeItemPart::fetchFromCache(Model *const model)
1613 {
1614     if (fetched() || loading() || isUnavailable())
1615         return;
1616 
1617     model->askForMsgPart(this, true);
1618 }
1619 
rowCount(Model * const model)1620 unsigned int TreeItemPart::rowCount(Model *const model)
1621 {
1622     // no call to fetch() required
1623     Q_UNUSED(model);
1624     return m_children.size();
1625 }
1626 
data(Model * const model,int role)1627 QVariant TreeItemPart::data(Model *const model, int role)
1628 {
1629     if (!parent())
1630         return QVariant();
1631 
1632     // these data are available immediately
1633     switch (role) {
1634     case RoleIsFetched:
1635         return fetched();
1636     case RoleIsUnavailable:
1637         return isUnavailable();
1638     case RolePartMimeType:
1639         return m_mimeType;
1640     case RolePartCharset:
1641         return m_charset;
1642     case RolePartContentFormat:
1643         return m_contentFormat;
1644     case RolePartContentDelSp:
1645         return m_delSp;
1646     case RolePartEncoding:
1647         return m_encoding;
1648     case RolePartBodyFldId:
1649         return m_bodyFldId;
1650     case RolePartBodyDisposition:
1651         return m_bodyDisposition;
1652     case RolePartFileName:
1653         return m_fileName;
1654     case RolePartOctets:
1655         return QVariant::fromValue(m_octets);
1656     case RolePartId:
1657         return partId();
1658     case RolePartPathToPart:
1659         return pathToPart();
1660     case RolePartMultipartRelatedMainCid:
1661         if (!multipartRelatedStartPart().isEmpty())
1662             return multipartRelatedStartPart();
1663         else
1664             return QVariant();
1665     case RolePartMessageIndex:
1666         return QVariant::fromValue(message()->toIndex(model));
1667     case RoleMailboxName:
1668     case RoleMailboxUidValidity:
1669         return message()->parent()->parent()->data(model, role);
1670     case RoleMessageUid:
1671         return message()->uid();
1672     case RolePartIsTopLevelMultipart:
1673         return isTopLevelMultiPart();
1674     case RolePartForceFetchFromCache:
1675         fetchFromCache(model);
1676         return QVariant();
1677     case RolePartBufferPtr:
1678         return QVariant::fromValue(dataPtr());
1679     case RolePartBodyFldParam:
1680         return QVariant::fromValue(m_bodyFldParam);
1681     }
1682 
1683 
1684     fetch(model);
1685 
1686     if (loading()) {
1687         if (role == Qt::DisplayRole) {
1688             return isTopLevelMultiPart() ?
1689                    Model::tr("[loading %1...]").arg(QString::fromUtf8(m_mimeType)) :
1690                    Model::tr("[loading %1: %2...]").arg(QString::fromUtf8(partId()), QString::fromUtf8(m_mimeType));
1691         } else {
1692             return QVariant();
1693         }
1694     }
1695 
1696     switch (role) {
1697     case Qt::DisplayRole:
1698         return isTopLevelMultiPart() ?
1699                QString::fromUtf8(m_mimeType) :
1700                QStringLiteral("%1: %2").arg(QString::fromUtf8(partId()), QString::fromUtf8(m_mimeType));
1701     case Qt::ToolTipRole:
1702         return QStringLiteral("%1 bytes of data").arg(m_data.size());
1703     case RolePartData:
1704         return m_data;
1705     case RolePartUnicodeText:
1706         if (m_mimeType.startsWith("text/")) {
1707             return decodeByteArray(m_data, m_charset);
1708         } else {
1709             return QVariant();
1710         }
1711     default:
1712         return QVariant();
1713     }
1714 }
1715 
hasChildren(Model * const model)1716 bool TreeItemPart::hasChildren(Model *const model)
1717 {
1718     // no need to fetch() here
1719     Q_UNUSED(model);
1720     return ! m_children.isEmpty();
1721 }
1722 
1723 /** @short Returns true if we're a multipart, top-level item in the body of a message */
isTopLevelMultiPart() const1724 bool TreeItemPart::isTopLevelMultiPart() const
1725 {
1726     TreeItemMessage *msg = dynamic_cast<TreeItemMessage *>(parent());
1727     TreeItemPart *part = dynamic_cast<TreeItemPart *>(parent());
1728     return m_mimeType.startsWith("multipart/") && (msg || (part && part->m_mimeType.startsWith("message/")));
1729 }
1730 
partId() const1731 QByteArray TreeItemPart::partId() const
1732 {
1733     if (isTopLevelMultiPart()) {
1734         return QByteArray();
1735     } else if (dynamic_cast<TreeItemMessage *>(parent())) {
1736         return QByteArray::number(row() + 1);
1737     } else {
1738         QByteArray parentId;
1739         TreeItemPart *parentPart = dynamic_cast<TreeItemPart *>(parent());
1740         Q_ASSERT(parentPart);
1741         if (parentPart->isTopLevelMultiPart()) {
1742             if (TreeItemPart *parentOfParent = dynamic_cast<TreeItemPart *>(parentPart->parent())) {
1743                 Q_ASSERT(!parentOfParent->isTopLevelMultiPart());
1744                 // grand parent: message/rfc822 with a part-id, parent: top-level multipart
1745                 parentId = parentOfParent->partId();
1746             } else {
1747                 // grand parent: TreeItemMessage, parent: some multipart, me: some part
1748                 return QByteArray::number(row() + 1);
1749             }
1750         } else {
1751             parentId = parentPart->partId();
1752         }
1753         Q_ASSERT(!parentId.isEmpty());
1754         return parentId + '.' + QByteArray::number(row() + 1);
1755     }
1756 }
1757 
partIdForFetch(const PartFetchingMode mode) const1758 QByteArray TreeItemPart::partIdForFetch(const PartFetchingMode mode) const
1759 {
1760     return QByteArray(mode == FETCH_PART_BINARY ? "BINARY" : "BODY") + ".PEEK[" + partId() + "]";
1761 }
1762 
pathToPart() const1763 QByteArray TreeItemPart::pathToPart() const
1764 {
1765     TreeItemPart *part = dynamic_cast<TreeItemPart *>(parent());
1766     TreeItemMessage *msg = dynamic_cast<TreeItemMessage *>(parent());
1767     if (part)
1768         return part->pathToPart() + '/' + QByteArray::number(row());
1769     else if (msg)
1770         return '/' + QByteArray::number(row());
1771     else {
1772         Q_ASSERT(false);
1773         return QByteArray();
1774     }
1775 }
1776 
message() const1777 TreeItemMessage *TreeItemPart::message() const
1778 {
1779     const TreeItemPart *part = this;
1780     while (part) {
1781         TreeItemMessage *message = dynamic_cast<TreeItemMessage *>(part->parent());
1782         if (message)
1783             return message;
1784         part = dynamic_cast<TreeItemPart *>(part->parent());
1785     }
1786     return 0;
1787 }
1788 
dataPtr()1789 QByteArray *TreeItemPart::dataPtr()
1790 {
1791     return &m_data;
1792 }
1793 
columnCount()1794 unsigned int TreeItemPart::columnCount()
1795 {
1796     if (isTopLevelMultiPart()) {
1797         // Because a top-level multipart doesn't have its own part number, one cannot really fetch from it
1798         return 1;
1799     }
1800 
1801     // This one includes the OFFSET_MIME and OFFSET_RAW_CONTENTS, unlike the TreeItemMessage
1802     static_assert(OFFSET_MIME < OFFSET_RAW_CONTENTS, "The OFFSET_RAW_CONTENTS shall be the biggest one for tree invariants to work");
1803     return OFFSET_RAW_CONTENTS + 1;
1804 }
1805 
specialColumnPtr(int row,int column) const1806 TreeItem *TreeItemPart::specialColumnPtr(int row, int column) const
1807 {
1808     if (row == 0 && !isTopLevelMultiPart()) {
1809         switch (column) {
1810         case OFFSET_MIME:
1811             if (!m_partMime) {
1812                 m_partMime = new TreeItemModifiedPart(const_cast<TreeItemPart*>(this), OFFSET_MIME);
1813             }
1814             return m_partMime;
1815         case OFFSET_RAW_CONTENTS:
1816             if (!m_partRaw) {
1817                 m_partRaw = new TreeItemModifiedPart(const_cast<TreeItemPart*>(this), OFFSET_RAW_CONTENTS);
1818             }
1819             return m_partRaw;
1820         }
1821     }
1822     return 0;
1823 }
1824 
silentlyReleaseMemoryRecursive()1825 void TreeItemPart::silentlyReleaseMemoryRecursive()
1826 {
1827     Q_FOREACH(TreeItem *item, m_children) {
1828         TreeItemPart *part = dynamic_cast<TreeItemPart *>(item);
1829         Q_ASSERT(part);
1830         part->silentlyReleaseMemoryRecursive();
1831     }
1832     if (m_partMime) {
1833         m_partMime->silentlyReleaseMemoryRecursive();
1834         delete m_partMime;
1835         m_partMime = 0;
1836     }
1837     if (m_partRaw) {
1838         m_partRaw->silentlyReleaseMemoryRecursive();
1839         delete m_partRaw;
1840         m_partRaw = 0;
1841     }
1842     m_data.clear();
1843     setFetchStatus(NONE);
1844     qDeleteAll(m_children);
1845     m_children.clear();
1846 }
1847 
1848 
1849 
TreeItemModifiedPart(TreeItem * parent,const PartModifier kind)1850 TreeItemModifiedPart::TreeItemModifiedPart(TreeItem *parent, const PartModifier kind):
1851     TreeItemPart(parent), m_modifier(kind)
1852 {
1853 }
1854 
row() const1855 int TreeItemModifiedPart::row() const
1856 {
1857     // we're always at the very top
1858     return 0;
1859 }
1860 
specialColumnPtr(int row,int column) const1861 TreeItem *TreeItemModifiedPart::specialColumnPtr(int row, int column) const
1862 {
1863     Q_UNUSED(row);
1864     Q_UNUSED(column);
1865     // no special children below the current special one
1866     return 0;
1867 }
1868 
isTopLevelMultiPart() const1869 bool TreeItemModifiedPart::isTopLevelMultiPart() const
1870 {
1871     // we're special enough not to ever act like a "top-level multipart"
1872     return false;
1873 }
1874 
columnCount()1875 unsigned int TreeItemModifiedPart::columnCount()
1876 {
1877     // no child items, either
1878     return 0;
1879 }
1880 
partId() const1881 QByteArray TreeItemModifiedPart::partId() const
1882 {
1883     if (m_modifier == OFFSET_RAW_CONTENTS) {
1884         // This item is not directly fetcheable, so it does *not* make sense to ask for it.
1885         // We cannot really assert at this point, though, because this function is published via the MVC interface.
1886         return "application-bug-dont-fetch-this";
1887     } else if (TreeItemPart *part = dynamic_cast<TreeItemPart *>(parent())) {
1888         // The TreeItemPart is supposed to prevent creation of any special subparts if it's a top-level multipart
1889         Q_ASSERT(!part->isTopLevelMultiPart());
1890         return part->partId() + '.' + modifierToByteArray();
1891     } else {
1892         // Our parent is a message/rfc822, and it's definitely not nested -> no need for parent id here
1893         // Cannot assert() on a dynamic_cast<TreeItemMessage*> at this point because the part is already nullptr at this time
1894         Q_ASSERT(dynamic_cast<TreeItemMessage*>(parent()));
1895         return modifierToByteArray();
1896     }
1897 }
1898 
kind() const1899 TreeItem::PartModifier TreeItemModifiedPart::kind() const
1900 {
1901     return m_modifier;
1902 }
1903 
modifierToByteArray() const1904 QByteArray TreeItemModifiedPart::modifierToByteArray() const
1905 {
1906     switch (m_modifier) {
1907     case OFFSET_HEADER:
1908         return "HEADER";
1909     case OFFSET_TEXT:
1910         return "TEXT";
1911     case OFFSET_MIME:
1912         return "MIME";
1913     case OFFSET_RAW_CONTENTS:
1914         Q_ASSERT(!"Cannot get the fetch modifier for an OFFSET_RAW_CONTENTS item");
1915         // fall through
1916     default:
1917         Q_ASSERT(false);
1918         return QByteArray();
1919     }
1920 }
1921 
pathToPart() const1922 QByteArray TreeItemModifiedPart::pathToPart() const
1923 {
1924     if (TreeItemPart *parentPart = dynamic_cast<TreeItemPart *>(parent())) {
1925         return parentPart->pathToPart() + "/" + modifierToByteArray();
1926     } else {
1927         Q_ASSERT(dynamic_cast<TreeItemMessage *>(parent()));
1928         return "/" + modifierToByteArray();
1929     }
1930 }
1931 
toIndex(Model * const model) const1932 QModelIndex TreeItemModifiedPart::toIndex(Model *const model) const
1933 {
1934     Q_ASSERT(model);
1935     // see TreeItem::toIndex() for the const_cast explanation
1936     return model->createIndex(row(), static_cast<int>(kind()), const_cast<TreeItemModifiedPart *>(this));
1937 }
1938 
partIdForFetch(const PartFetchingMode mode) const1939 QByteArray TreeItemModifiedPart::partIdForFetch(const PartFetchingMode mode) const
1940 {
1941     Q_UNUSED(mode);
1942     // Don't try to use BINARY for special message parts, it's forbidden. One can only use that for the "regular" MIME parts
1943     return TreeItemPart::partIdForFetch(FETCH_PART_IMAP);
1944 }
1945 
TreeItemPartMultipartMessage(TreeItem * parent,const Message::Envelope & envelope)1946 TreeItemPartMultipartMessage::TreeItemPartMultipartMessage(TreeItem *parent, const Message::Envelope &envelope):
1947     TreeItemPart(parent, "message/rfc822"), m_envelope(envelope), m_partHeader(0), m_partText(0)
1948 {
1949 }
1950 
~TreeItemPartMultipartMessage()1951 TreeItemPartMultipartMessage::~TreeItemPartMultipartMessage()
1952 {
1953 }
1954 
1955 /** @short Overridden from TreeItemPart::data with added support for RoleMessageEnvelope */
data(Model * const model,int role)1956 QVariant TreeItemPartMultipartMessage::data(Model * const model, int role)
1957 {
1958     switch (role) {
1959     case RoleMessageEnvelope:
1960         return QVariant::fromValue<Message::Envelope>(m_envelope);
1961     case RoleMessageHeaderReferences:
1962     case RoleMessageHeaderListPost:
1963     case RoleMessageHeaderListPostNo:
1964         // FIXME: implement me; TreeItemPart has no path for this
1965         return QVariant();
1966     default:
1967         return TreeItemPart::data(model, role);
1968     }
1969 }
1970 
specialColumnPtr(int row,int column) const1971 TreeItem *TreeItemPartMultipartMessage::specialColumnPtr(int row, int column) const
1972 {
1973     if (row != 0)
1974         return 0;
1975     switch (column) {
1976     case OFFSET_HEADER:
1977         if (!m_partHeader) {
1978             m_partHeader = new TreeItemModifiedPart(const_cast<TreeItemPartMultipartMessage*>(this), OFFSET_HEADER);
1979         }
1980         return m_partHeader;
1981     case OFFSET_TEXT:
1982         if (!m_partText) {
1983             m_partText = new TreeItemModifiedPart(const_cast<TreeItemPartMultipartMessage*>(this), OFFSET_TEXT);
1984         }
1985         return m_partText;
1986     default:
1987         return TreeItemPart::specialColumnPtr(row, column);
1988     }
1989 }
1990 
silentlyReleaseMemoryRecursive()1991 void TreeItemPartMultipartMessage::silentlyReleaseMemoryRecursive()
1992 {
1993     TreeItemPart::silentlyReleaseMemoryRecursive();
1994     if (m_partHeader) {
1995         m_partHeader->silentlyReleaseMemoryRecursive();
1996         delete m_partHeader;
1997         m_partHeader = 0;
1998     }
1999     if (m_partText) {
2000         m_partText->silentlyReleaseMemoryRecursive();
2001         delete m_partText;
2002         m_partText = 0;
2003     }
2004 }
2005 
2006 }
2007 }
2008