SVN_SILENT made messages (.desktop file) - always resolve ours
[trojita.git] / src / UiUtils / PartWalker_impl.h
blobbada096a689188f67a4adc7ba5ff85f792ae4e02
1 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
3 This file is part of the Trojita Qt IMAP e-mail client,
4 http://trojita.flaska.net/
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.
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.
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/>.
23 #include "UiUtils/PartWalker.h"
24 #include "Imap/Model/ItemRoles.h"
25 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
26 # include <QMimeDatabase>
27 #else
28 # include "mimetypes-qt4/include/QMimeDatabase"
29 #endif
31 namespace UiUtils {
33 template<typename Result, typename Context>
34 PartWalker<Result, Context>::PartWalker(Imap::Network::MsgPartNetAccessManager *manager,
35 Context context, std::unique_ptr<PartVisitor<Result, Context> > visitor)
36 : m_manager(manager), m_netWatcher(0), m_context(context)
38 m_visitor = std::move(visitor);
41 template<typename Result, typename Context>
42 Result PartWalker<Result, Context>::walk(const QModelIndex &partIndex,int recursionDepth, const UiUtils::PartLoadingOptions loadingMode)
44 using namespace Imap::Mailbox;
45 Q_ASSERT(partIndex.isValid());
47 if (recursionDepth > 1000) {
48 return m_visitor->visitError(PartWalker::tr("This message contains too deep nesting of MIME message parts.\n"
49 "To prevent stack exhaustion and your head from exploding, only\n"
50 "the top-most thousand items or so are shown."), 0);
53 QString mimeType = partIndex.data(Imap::Mailbox::RolePartMimeType).toString().toLower();
54 bool isMessageRfc822 = mimeType == QLatin1String("message/rfc822");
55 bool isCompoundMimeType = mimeType.startsWith(QLatin1String("multipart/")) || isMessageRfc822;
57 if (loadingMode & PART_IS_HIDDEN) {
58 return m_visitor->visitLoadablePart(0, m_manager, partIndex, this, recursionDepth + 1,
59 loadingMode | PART_IGNORE_CLICKTHROUGH);
62 // Check whether we can render this MIME type at all
63 QStringList allowedMimeTypes;
64 allowedMimeTypes << QLatin1String("text/html") << QLatin1String("text/plain") << QLatin1String("image/jpeg") <<
65 QLatin1String("image/jpg") << QLatin1String("image/pjpeg") << QLatin1String("image/png") << QLatin1String("image/gif");
66 bool recognizedMimeType = isCompoundMimeType || allowedMimeTypes.contains(mimeType);
67 bool isDerivedMimeType = false;
68 if (!recognizedMimeType) {
69 // QMimeType's docs say that one shall use inherit() to check for "is this a recognized MIME type".
70 // E.g. text/x-csrc inherits text/plain.
71 QMimeType partType = QMimeDatabase().mimeTypeForName(mimeType);
72 Q_FOREACH(const QString &candidate, allowedMimeTypes) {
73 if (partType.isValid() && !partType.isDefault() && partType.inherits(candidate)) {
74 // Looks like we shall be able to show this
75 recognizedMimeType = true;
76 // If it's a derived MIME type, it makes sense to not block inline display, yet still make it possible to hide it
77 // using the regular attachment controls
78 isDerivedMimeType = true;
79 m_manager->registerMimeTypeTranslation(mimeType, candidate);
80 break;
85 // Check whether it shall be wrapped inside an AttachmentView
86 // From section 2.8 of RFC 2183: "Unrecognized disposition types should be treated as `attachment'."
87 const QByteArray contentDisposition = partIndex.data(Imap::Mailbox::RolePartBodyDisposition).toByteArray().toLower();
88 const bool isInline = contentDisposition.isEmpty() || contentDisposition == "inline";
89 const bool looksLikeAttachment = !partIndex.data(Imap::Mailbox::RolePartFileName).toString().isEmpty();
90 const bool wrapInAttachmentView = !(loadingMode & PART_IGNORE_DISPOSITION_ATTACHMENT)
91 && (looksLikeAttachment || !isInline || !recognizedMimeType || isDerivedMimeType || isMessageRfc822);
92 if (wrapInAttachmentView) {
93 // The problem is that some nasty MUAs (hint hint Thunderbird) would
94 // happily attach a .tar.gz and call it "inline"
95 Result contentView = 0;
96 if (recognizedMimeType) {
97 PartLoadingOptions options = loadingMode | PART_IGNORE_DISPOSITION_ATTACHMENT;
98 if (!isInline) {
99 // The widget will be hidden by default, i.e. the "inline preview" will be deactivated.
100 // If the user clicks that action in the AttachmentView, it makes sense to load the plugin without any further ado,
101 // without requiring an extra clickthrough
102 options |= PART_IS_HIDDEN;
103 } else if (!isCompoundMimeType) {
104 // This is to prevent a clickthrough when the data can be already shown
105 partIndex.data(Imap::Mailbox::RolePartForceFetchFromCache);
107 // This makes sure that clickthrough only affects big parts during "expensive network" mode
108 if ( (m_netWatcher && m_netWatcher->desiredNetworkPolicy() != Imap::Mailbox::NETWORK_EXPENSIVE)
109 || partIndex.data(Imap::Mailbox::RolePartOctets).toInt() <= ExpensiveFetchThreshold) {
110 options |= PART_IGNORE_CLICKTHROUGH;
112 } else {
113 // A compound type -> make sure we disable clickthrough
114 options |= PART_IGNORE_CLICKTHROUGH;
117 if (m_netWatcher && m_netWatcher->effectiveNetworkPolicy() == Imap::Mailbox::NETWORK_OFFLINE) {
118 // This is to prevent a clickthrough when offline
119 options |= PART_IGNORE_CLICKTHROUGH;
121 contentView = m_visitor->visitLoadablePart(0, m_manager, partIndex, this, recursionDepth + 1, options);
122 if (!isInline) {
123 m_visitor->applySetHidden(contentView);
126 // Previously, we would also hide an attachment if the current policy was "expensive network", the part was too big
127 // and not fetched yet. Arguably, that's a bug -- an item which is marked online shall not be hidden.
128 // Wrapping via a clickthrough is fine, though; the user is clearly informed that this item *should* be visible,
129 // yet the bandwidth is not excessively trashed.
130 return m_visitor->visitAttachmentPart(0, m_manager, partIndex, m_context, contentView);
133 // Now we know for sure that it's not supposed to be wrapped in an AttachmentView, cool.
134 if (mimeType.startsWith(QLatin1String("multipart/"))) {
135 // it's a compound part
136 if (mimeType == QLatin1String("multipart/alternative")) {
137 return m_visitor->visitMultipartAlternative(0, this, partIndex, recursionDepth, loadingMode);
138 } else if (mimeType == QLatin1String("multipart/signed")) {
139 return m_visitor->visitMultipartSignedView(0, this, partIndex, recursionDepth, loadingMode);
140 } else if (mimeType == QLatin1String("multipart/related")) {
141 // The purpose of this section is to find a text/html e-mail, along with its associated body parts, and hide
142 // everything else than the HTML widget.
144 // At this point, it might be interesting to somehow respect the user's preference about using text/plain
145 // instead of text/html. However, things are a bit complicated; the widget used at this point really wants
146 // to either show just a single part or alternatively all of them in a sequence.
147 // Furthermore, if someone sends a text/plain and a text/html together inside a multipart/related, they're
148 // just wrong.
150 // Let's see if we know what the root part is
151 QModelIndex mainPartIndex;
152 QVariant mainPartCID = partIndex.data(RolePartMultipartRelatedMainCid);
153 if (mainPartCID.isValid()) {
154 mainPartIndex = Imap::Network::MsgPartNetAccessManager::cidToPart(partIndex, mainPartCID.toByteArray());
157 if (!mainPartIndex.isValid()) {
158 // The Content-Type-based start parameter was not terribly useful. Let's find the HTML part manually.
159 QModelIndex candidate = partIndex.child(0, 0);
160 while (candidate.isValid()) {
161 if (candidate.data(RolePartMimeType).toString() == QLatin1String("text/html")) {
162 mainPartIndex = candidate;
163 break;
165 candidate = candidate.sibling(candidate.row() + 1, 0);
169 if (mainPartIndex.isValid()) {
170 if (mainPartIndex.data(RolePartMimeType).toString() == QLatin1String("text/html")) {
171 return walk(mainPartIndex, recursionDepth+1, loadingMode);
172 } else {
173 // Sorry, but anything else than text/html is by definition suspicious here. Better than picking some random
174 // choice, let's just show everything.
175 return m_visitor->visitGenericMultipartView(0, this, partIndex, recursionDepth, loadingMode);
177 } else {
178 // The RFC2387's wording is clear that in absence of an explicit START argument, the first part is the starting one.
179 // On the other hand, I've seen real-world messages whose first part is some utter garbage (an image sent as
180 // application/octet-stream, for example) and some *other* part is an HTML text. In that case (and if we somehow
181 // failed to pick the HTML part by a heuristic), it's better to show everything.
182 return m_visitor->visitGenericMultipartView(0, this, partIndex, recursionDepth, loadingMode);
184 } else {
185 return m_visitor->visitGenericMultipartView(0, this, partIndex, recursionDepth, loadingMode);
187 } else if (mimeType == QLatin1String("message/rfc822")) {
188 return m_visitor->visitMessage822View(0, this, partIndex, recursionDepth, loadingMode);
189 } else {
190 partIndex.data(Imap::Mailbox::RolePartForceFetchFromCache);
192 if ((loadingMode & PART_IGNORE_CLICKTHROUGH) || (loadingMode & PART_IGNORE_LOAD_ON_SHOW) ||
193 partIndex.data(Imap::Mailbox::RoleIsFetched).toBool() ||
194 (m_netWatcher && m_netWatcher->desiredNetworkPolicy() != Imap::Mailbox::NETWORK_EXPENSIVE ) ||
195 partIndex.data(Imap::Mailbox::RolePartOctets).toInt() < ExpensiveFetchThreshold) {
196 // Show it directly without any fancy wrapping
197 return m_visitor->visitSimplePartView(0, m_manager, partIndex, m_context);
198 } else {
199 return m_visitor->visitLoadablePart(0, m_manager, partIndex, this, recursionDepth + 1,
200 (m_netWatcher && m_netWatcher->effectiveNetworkPolicy() != Imap::Mailbox::NETWORK_OFFLINE) ?
201 loadingMode : loadingMode | PART_IGNORE_CLICKTHROUGH);
206 template<typename Result, typename Context>
207 Context PartWalker<Result, Context>::context() const
209 return m_context;
212 template<typename Result, typename Context>
213 void PartWalker<Result, Context>::setNetworkWatcher(Imap::Mailbox::NetworkWatcher *netWatcher)
215 m_netWatcher = netWatcher;