diff options
Diffstat (limited to 'framework/src/domain/mimetreeparser/otp/objecttreeparser.cpp')
-rw-r--r-- | framework/src/domain/mimetreeparser/otp/objecttreeparser.cpp | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/framework/src/domain/mimetreeparser/otp/objecttreeparser.cpp b/framework/src/domain/mimetreeparser/otp/objecttreeparser.cpp new file mode 100644 index 00000000..b0d514b6 --- /dev/null +++ b/framework/src/domain/mimetreeparser/otp/objecttreeparser.cpp | |||
@@ -0,0 +1,495 @@ | |||
1 | /* | ||
2 | objecttreeparser.cpp | ||
3 | |||
4 | This file is part of KMail, the KDE mail client. | ||
5 | Copyright (c) 2003 Marc Mutz <mutz@kde.org> | ||
6 | Copyright (C) 2002-2004 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net | ||
7 | Copyright (c) 2009 Andras Mantia <andras@kdab.net> | ||
8 | Copyright (c) 2015 Sandro Knauß <sknauss@kde.org> | ||
9 | |||
10 | KMail is free software; you can redistribute it and/or modify it | ||
11 | under the terms of the GNU General Public License, version 2, as | ||
12 | published by the Free Software Foundation. | ||
13 | |||
14 | KMail is distributed in the hope that it will be useful, but | ||
15 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
17 | 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, write to the Free Software | ||
21 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
22 | |||
23 | In addition, as a special exception, the copyright holders give | ||
24 | permission to link the code of this program with any edition of | ||
25 | the Qt library by Trolltech AS, Norway (or with modified versions | ||
26 | of Qt that use the same license as Qt), and distribute linked | ||
27 | combinations including the two. You must obey the GNU General | ||
28 | Public License in all respects for all of the code used other than | ||
29 | Qt. If you modify this file, you may extend this exception to | ||
30 | your version of the file, but you are not obligated to do so. If | ||
31 | you do not wish to do so, delete this exception statement from | ||
32 | your version. | ||
33 | */ | ||
34 | |||
35 | // MessageViewer includes | ||
36 | |||
37 | #include "objecttreeparser.h" | ||
38 | |||
39 | #include "attachmentstrategy.h" | ||
40 | #include "bodypartformatterbasefactory.h" | ||
41 | #include "nodehelper.h" | ||
42 | #include "messagepart.h" | ||
43 | #include "partnodebodypart.h" | ||
44 | |||
45 | #include "mimetreeparser_debug.h" | ||
46 | |||
47 | #include "utils.h" | ||
48 | #include "bodypartformatter.h" | ||
49 | #include "htmlwriter.h" | ||
50 | #include "messagepartrenderer.h" | ||
51 | #include "util.h" | ||
52 | |||
53 | #include <KMime/Headers> | ||
54 | #include <KMime/Message> | ||
55 | |||
56 | // KDE includes | ||
57 | |||
58 | // Qt includes | ||
59 | #include <QByteArray> | ||
60 | #include <QTextCodec> | ||
61 | #include <QUrl> | ||
62 | |||
63 | using namespace MimeTreeParser; | ||
64 | |||
65 | ObjectTreeParser::ObjectTreeParser(const ObjectTreeParser *topLevelParser, | ||
66 | bool showOnlyOneMimePart, | ||
67 | const AttachmentStrategy *strategy) | ||
68 | : mSource(topLevelParser->mSource), | ||
69 | mNodeHelper(topLevelParser->mNodeHelper), | ||
70 | mHtmlWriter(topLevelParser->mHtmlWriter), | ||
71 | mTopLevelContent(topLevelParser->mTopLevelContent), | ||
72 | mShowOnlyOneMimePart(showOnlyOneMimePart), | ||
73 | mHasPendingAsyncJobs(false), | ||
74 | mAllowAsync(topLevelParser->mAllowAsync), | ||
75 | mAttachmentStrategy(strategy) | ||
76 | { | ||
77 | init(); | ||
78 | } | ||
79 | |||
80 | ObjectTreeParser::ObjectTreeParser(Interface::ObjectTreeSource *source, | ||
81 | MimeTreeParser::NodeHelper *nodeHelper, | ||
82 | bool showOnlyOneMimePart, | ||
83 | const AttachmentStrategy *strategy) | ||
84 | : mSource(source), | ||
85 | mNodeHelper(nodeHelper), | ||
86 | mHtmlWriter(nullptr), | ||
87 | mTopLevelContent(nullptr), | ||
88 | mShowOnlyOneMimePart(showOnlyOneMimePart), | ||
89 | mHasPendingAsyncJobs(false), | ||
90 | mAllowAsync(false), | ||
91 | mAttachmentStrategy(strategy) | ||
92 | { | ||
93 | init(); | ||
94 | } | ||
95 | |||
96 | void ObjectTreeParser::init() | ||
97 | { | ||
98 | Q_ASSERT(mSource); | ||
99 | if (!attachmentStrategy()) { | ||
100 | mAttachmentStrategy = mSource->attachmentStrategy(); | ||
101 | } | ||
102 | |||
103 | if (!mNodeHelper) { | ||
104 | mNodeHelper = new NodeHelper(); | ||
105 | mDeleteNodeHelper = true; | ||
106 | } else { | ||
107 | mDeleteNodeHelper = false; | ||
108 | } | ||
109 | } | ||
110 | |||
111 | ObjectTreeParser::ObjectTreeParser(const ObjectTreeParser &other) | ||
112 | : mSource(other.mSource), | ||
113 | mNodeHelper(other.nodeHelper()), //TODO(Andras) hm, review what happens if mDeleteNodeHelper was true in the source | ||
114 | mHtmlWriter(other.mHtmlWriter), | ||
115 | mTopLevelContent(other.mTopLevelContent), | ||
116 | mShowOnlyOneMimePart(other.showOnlyOneMimePart()), | ||
117 | mHasPendingAsyncJobs(other.hasPendingAsyncJobs()), | ||
118 | mAllowAsync(other.allowAsync()), | ||
119 | mAttachmentStrategy(other.attachmentStrategy()), | ||
120 | mDeleteNodeHelper(false) | ||
121 | { | ||
122 | |||
123 | } | ||
124 | |||
125 | ObjectTreeParser::~ObjectTreeParser() | ||
126 | { | ||
127 | if (mDeleteNodeHelper) { | ||
128 | delete mNodeHelper; | ||
129 | mNodeHelper = nullptr; | ||
130 | } | ||
131 | } | ||
132 | |||
133 | void ObjectTreeParser::setAllowAsync(bool allow) | ||
134 | { | ||
135 | Q_ASSERT(!mHasPendingAsyncJobs); | ||
136 | mAllowAsync = allow; | ||
137 | } | ||
138 | |||
139 | bool ObjectTreeParser::allowAsync() const | ||
140 | { | ||
141 | return mAllowAsync; | ||
142 | } | ||
143 | |||
144 | bool ObjectTreeParser::hasPendingAsyncJobs() const | ||
145 | { | ||
146 | return mHasPendingAsyncJobs; | ||
147 | } | ||
148 | |||
149 | QString ObjectTreeParser::plainTextContent() const | ||
150 | { | ||
151 | return mPlainTextContent; | ||
152 | } | ||
153 | |||
154 | QString ObjectTreeParser::htmlContent() const | ||
155 | { | ||
156 | return mHtmlContent; | ||
157 | } | ||
158 | |||
159 | void ObjectTreeParser::copyContentFrom(const ObjectTreeParser *other) | ||
160 | { | ||
161 | mPlainTextContent += other->plainTextContent(); | ||
162 | mHtmlContent += other->htmlContent(); | ||
163 | if (!other->plainTextContentCharset().isEmpty()) { | ||
164 | mPlainTextContentCharset = other->plainTextContentCharset(); | ||
165 | } | ||
166 | if (!other->htmlContentCharset().isEmpty()) { | ||
167 | mHtmlContentCharset = other->htmlContentCharset(); | ||
168 | } | ||
169 | } | ||
170 | |||
171 | //----------------------------------------------------------------------------- | ||
172 | |||
173 | void ObjectTreeParser::parseObjectTree(KMime::Content *node) | ||
174 | { | ||
175 | mTopLevelContent = node; | ||
176 | mParsedPart = parseObjectTreeInternal(node, showOnlyOneMimePart()); | ||
177 | |||
178 | if (mParsedPart) { | ||
179 | mParsedPart->fix(); | ||
180 | mParsedPart->copyContentFrom(); | ||
181 | if (auto mp = toplevelTextNode(mParsedPart)) { | ||
182 | if (auto _mp = mp.dynamicCast<TextMessagePart>()) { | ||
183 | extractNodeInfos(_mp->mNode, true); | ||
184 | } else if (auto _mp = mp.dynamicCast<AlternativeMessagePart>()) { | ||
185 | if (_mp->mChildNodes.contains(Util::MultipartPlain)) { | ||
186 | extractNodeInfos(_mp->mChildNodes[Util::MultipartPlain], true); | ||
187 | } | ||
188 | } | ||
189 | setPlainTextContent(mp->text()); | ||
190 | } | ||
191 | |||
192 | if (htmlWriter()) { | ||
193 | const auto renderer = mSource->messagePartTheme(mParsedPart); | ||
194 | if (renderer) { | ||
195 | mHtmlWriter->queue(renderer->html()); | ||
196 | } | ||
197 | } | ||
198 | } | ||
199 | } | ||
200 | |||
201 | MessagePartPtr ObjectTreeParser::parsedPart() const | ||
202 | { | ||
203 | return mParsedPart; | ||
204 | } | ||
205 | |||
206 | bool ObjectTreeParser::processType(KMime::Content *node, ProcessResult &processResult, const QByteArray &mediaType, const QByteArray &subType, Interface::MessagePartPtr &mpRet, bool onlyOneMimePart) | ||
207 | { | ||
208 | bool bRendered = false; | ||
209 | const auto sub = mSource->bodyPartFormatterFactory()->subtypeRegistry(mediaType.constData()); | ||
210 | auto range = sub.equal_range(subType.constData()); | ||
211 | for (auto it = range.first; it != range.second; ++it) { | ||
212 | const auto formatter = (*it).second; | ||
213 | if (!formatter) { | ||
214 | continue; | ||
215 | } | ||
216 | PartNodeBodyPart part(this, &processResult, mTopLevelContent, node, mNodeHelper); | ||
217 | // Set the default display strategy for this body part relying on the | ||
218 | // identity of Interface::BodyPart::Display and AttachmentStrategy::Display | ||
219 | part.setDefaultDisplay((Interface::BodyPart::Display) attachmentStrategy()->defaultDisplay(node)); | ||
220 | |||
221 | mNodeHelper->setNodeDisplayedEmbedded(node, true); | ||
222 | |||
223 | const Interface::MessagePart::Ptr result = formatter->process(part); | ||
224 | if (!result) { | ||
225 | continue; | ||
226 | } | ||
227 | |||
228 | if (const auto mp = result.dynamicCast<MessagePart>()) { | ||
229 | mp->setAttachmentFlag(node); | ||
230 | mpRet = result; | ||
231 | bRendered = true; | ||
232 | break; | ||
233 | } else if (dynamic_cast<MimeTreeParser::Interface::MessagePart *>(result.data())) { | ||
234 | QObject *asyncResultObserver = allowAsync() ? mSource->sourceObject() : nullptr; | ||
235 | const auto r = formatter->format(&part, result->htmlWriter(), asyncResultObserver); | ||
236 | if (r == Interface::BodyPartFormatter::AsIcon) { | ||
237 | processResult.setNeverDisplayInline(true); | ||
238 | formatter->adaptProcessResult(processResult); | ||
239 | mNodeHelper->setNodeDisplayedEmbedded(node, false); | ||
240 | const Interface::MessagePart::Ptr mp = defaultHandling(node, processResult, onlyOneMimePart); | ||
241 | if (mp) { | ||
242 | if (auto _mp = mp.dynamicCast<MessagePart>()) { | ||
243 | _mp->setAttachmentFlag(node); | ||
244 | } | ||
245 | mpRet = mp; | ||
246 | } | ||
247 | bRendered = true; | ||
248 | break; | ||
249 | } else if (r == Interface::BodyPartFormatter::Ok) { | ||
250 | processResult.setNeverDisplayInline(true); | ||
251 | formatter->adaptProcessResult(processResult); | ||
252 | mpRet = result; | ||
253 | bRendered = true; | ||
254 | break; | ||
255 | } | ||
256 | continue; | ||
257 | } else { | ||
258 | continue; | ||
259 | } | ||
260 | } | ||
261 | return bRendered; | ||
262 | } | ||
263 | |||
264 | MessagePart::Ptr ObjectTreeParser::parseObjectTreeInternal(KMime::Content *node, bool onlyOneMimePart) | ||
265 | { | ||
266 | if (!node) { | ||
267 | return MessagePart::Ptr(); | ||
268 | } | ||
269 | |||
270 | // reset pending async jobs state (we'll rediscover pending jobs as we go) | ||
271 | mHasPendingAsyncJobs = false; | ||
272 | |||
273 | // reset "processed" flags for... | ||
274 | if (onlyOneMimePart) { | ||
275 | // ... this node and all descendants | ||
276 | mNodeHelper->setNodeUnprocessed(node, false); | ||
277 | if (!node->contents().isEmpty()) { | ||
278 | mNodeHelper->setNodeUnprocessed(node, true); | ||
279 | } | ||
280 | } else if (!node->parent()) { | ||
281 | // ...this node and all it's siblings and descendants | ||
282 | mNodeHelper->setNodeUnprocessed(node, true); | ||
283 | } | ||
284 | |||
285 | const bool isRoot = node->isTopLevel(); | ||
286 | auto parsedPart = MessagePart::Ptr(new MessagePartList(this)); | ||
287 | parsedPart->setIsRoot(isRoot); | ||
288 | KMime::Content *parent = node->parent(); | ||
289 | auto contents = parent ? parent->contents() : KMime::Content::List(); | ||
290 | if (contents.isEmpty()) { | ||
291 | contents.append(node); | ||
292 | } | ||
293 | int i = contents.indexOf(const_cast<KMime::Content *>(node)); | ||
294 | for (; i < contents.size(); ++i) { | ||
295 | node = contents.at(i); | ||
296 | if (mNodeHelper->nodeProcessed(node)) { | ||
297 | continue; | ||
298 | } | ||
299 | |||
300 | ProcessResult processResult(mNodeHelper); | ||
301 | |||
302 | QByteArray mediaType("text"); | ||
303 | QByteArray subType("plain"); | ||
304 | if (node->contentType(false) && !node->contentType()->mediaType().isEmpty() && | ||
305 | !node->contentType()->subType().isEmpty()) { | ||
306 | mediaType = node->contentType()->mediaType(); | ||
307 | subType = node->contentType()->subType(); | ||
308 | } | ||
309 | |||
310 | Interface::MessagePartPtr mp; | ||
311 | if (processType(node, processResult, mediaType, subType, mp, onlyOneMimePart)) { | ||
312 | if (mp) { | ||
313 | parsedPart->appendSubPart(mp); | ||
314 | } | ||
315 | } else if (processType(node, processResult, mediaType, "*", mp, onlyOneMimePart)) { | ||
316 | if (mp) { | ||
317 | parsedPart->appendSubPart(mp); | ||
318 | } | ||
319 | } else { | ||
320 | qCWarning(MIMETREEPARSER_LOG) << "THIS SHOULD NO LONGER HAPPEN:" << mediaType << '/' << subType; | ||
321 | const auto mp = defaultHandling(node, processResult, onlyOneMimePart); | ||
322 | if (mp) { | ||
323 | if (auto _mp = mp.dynamicCast<MessagePart>()) { | ||
324 | _mp->setAttachmentFlag(node); | ||
325 | } | ||
326 | parsedPart->appendSubPart(mp); | ||
327 | } | ||
328 | } | ||
329 | mNodeHelper->setNodeProcessed(node, false); | ||
330 | |||
331 | // adjust signed/encrypted flags if inline PGP was found | ||
332 | processResult.adjustCryptoStatesOfNode(node); | ||
333 | |||
334 | if (onlyOneMimePart) { | ||
335 | break; | ||
336 | } | ||
337 | } | ||
338 | |||
339 | return parsedPart; | ||
340 | } | ||
341 | |||
342 | Interface::MessagePart::Ptr ObjectTreeParser::defaultHandling(KMime::Content *node, ProcessResult &result, bool onlyOneMimePart) | ||
343 | { | ||
344 | Interface::MessagePart::Ptr mp; | ||
345 | ProcessResult processResult(mNodeHelper); | ||
346 | |||
347 | if (node->contentType()->mimeType() == QByteArrayLiteral("application/octet-stream") && | ||
348 | (node->contentType()->name().endsWith(QLatin1String("p7m")) || | ||
349 | node->contentType()->name().endsWith(QLatin1String("p7s")) || | ||
350 | node->contentType()->name().endsWith(QLatin1String("p7c")) | ||
351 | ) && | ||
352 | processType(node, processResult, "application", "pkcs7-mime", mp, onlyOneMimePart)) { | ||
353 | return mp; | ||
354 | } | ||
355 | |||
356 | const auto _mp = AttachmentMessagePart::Ptr(new AttachmentMessagePart(this, node, false, true, mSource->decryptMessage())); | ||
357 | result.setInlineSignatureState(_mp->signatureState()); | ||
358 | result.setInlineEncryptionState(_mp->encryptionState()); | ||
359 | _mp->setNeverDisplayInline(result.neverDisplayInline()); | ||
360 | _mp->setIsImage(result.isImage()); | ||
361 | mp = _mp; | ||
362 | |||
363 | // always show images in multipart/related when showing in html, not with an additional icon | ||
364 | auto preferredMode = mSource->preferredMode(); | ||
365 | bool isHtmlPreferred = (preferredMode == Util::Html) || (preferredMode == Util::MultipartHtml); | ||
366 | if (result.isImage() && node->parent() && | ||
367 | node->parent()->contentType()->subType() == "related" && isHtmlPreferred && !onlyOneMimePart) { | ||
368 | QString fileName = mNodeHelper->writeNodeToTempFile(node); | ||
369 | QString href = QUrl::fromLocalFile(fileName).url(); | ||
370 | QByteArray cid = node->contentID()->identifier(); | ||
371 | if (htmlWriter()) { | ||
372 | htmlWriter()->embedPart(cid, href); | ||
373 | } | ||
374 | nodeHelper()->setNodeDisplayedEmbedded(node, true); | ||
375 | mNodeHelper->setNodeDisplayedHidden(node, true); | ||
376 | return mp; | ||
377 | } | ||
378 | |||
379 | // Show it inline if showOnlyOneMimePart(), which means the user clicked the image | ||
380 | // in the message structure viewer manually, and therefore wants to see the full image | ||
381 | if (result.isImage() && onlyOneMimePart && !result.neverDisplayInline()) { | ||
382 | mNodeHelper->setNodeDisplayedEmbedded(node, true); | ||
383 | } | ||
384 | |||
385 | return mp; | ||
386 | } | ||
387 | |||
388 | KMMsgSignatureState ProcessResult::inlineSignatureState() const | ||
389 | { | ||
390 | return mInlineSignatureState; | ||
391 | } | ||
392 | |||
393 | void ProcessResult::setInlineSignatureState(KMMsgSignatureState state) | ||
394 | { | ||
395 | mInlineSignatureState = state; | ||
396 | } | ||
397 | |||
398 | KMMsgEncryptionState ProcessResult::inlineEncryptionState() const | ||
399 | { | ||
400 | return mInlineEncryptionState; | ||
401 | } | ||
402 | |||
403 | void ProcessResult::setInlineEncryptionState(KMMsgEncryptionState state) | ||
404 | { | ||
405 | mInlineEncryptionState = state; | ||
406 | } | ||
407 | |||
408 | bool ProcessResult::neverDisplayInline() const | ||
409 | { | ||
410 | return mNeverDisplayInline; | ||
411 | } | ||
412 | |||
413 | void ProcessResult::setNeverDisplayInline(bool display) | ||
414 | { | ||
415 | mNeverDisplayInline = display; | ||
416 | } | ||
417 | |||
418 | bool ProcessResult::isImage() const | ||
419 | { | ||
420 | return mIsImage; | ||
421 | } | ||
422 | |||
423 | void ProcessResult::setIsImage(bool image) | ||
424 | { | ||
425 | mIsImage = image; | ||
426 | } | ||
427 | |||
428 | void ProcessResult::adjustCryptoStatesOfNode(const KMime::Content *node) const | ||
429 | { | ||
430 | if ((inlineSignatureState() != KMMsgNotSigned) || | ||
431 | (inlineEncryptionState() != KMMsgNotEncrypted)) { | ||
432 | mNodeHelper->setSignatureState(node, inlineSignatureState()); | ||
433 | mNodeHelper->setEncryptionState(node, inlineEncryptionState()); | ||
434 | } | ||
435 | } | ||
436 | |||
437 | void ObjectTreeParser::extractNodeInfos(KMime::Content *curNode, bool isFirstTextPart) | ||
438 | { | ||
439 | if (isFirstTextPart) { | ||
440 | mPlainTextContent += curNode->decodedText(); | ||
441 | mPlainTextContentCharset += NodeHelper::charset(curNode); | ||
442 | } | ||
443 | } | ||
444 | |||
445 | void ObjectTreeParser::setPlainTextContent(const QString &plainTextContent) | ||
446 | { | ||
447 | mPlainTextContent = plainTextContent; | ||
448 | } | ||
449 | |||
450 | const QTextCodec *ObjectTreeParser::codecFor(KMime::Content *node) const | ||
451 | { | ||
452 | Q_ASSERT(node); | ||
453 | if (mSource->overrideCodec()) { | ||
454 | return mSource->overrideCodec(); | ||
455 | } | ||
456 | return mNodeHelper->codec(node); | ||
457 | } | ||
458 | |||
459 | QByteArray ObjectTreeParser::plainTextContentCharset() const | ||
460 | { | ||
461 | return mPlainTextContentCharset; | ||
462 | } | ||
463 | |||
464 | QByteArray ObjectTreeParser::htmlContentCharset() const | ||
465 | { | ||
466 | return mHtmlContentCharset; | ||
467 | } | ||
468 | |||
469 | bool ObjectTreeParser::showOnlyOneMimePart() const | ||
470 | { | ||
471 | return mShowOnlyOneMimePart; | ||
472 | } | ||
473 | |||
474 | void ObjectTreeParser::setShowOnlyOneMimePart(bool show) | ||
475 | { | ||
476 | mShowOnlyOneMimePart = show; | ||
477 | } | ||
478 | |||
479 | const AttachmentStrategy *ObjectTreeParser::attachmentStrategy() const | ||
480 | { | ||
481 | return mAttachmentStrategy; | ||
482 | } | ||
483 | |||
484 | HtmlWriter *ObjectTreeParser::htmlWriter() const | ||
485 | { | ||
486 | if (mHtmlWriter) { | ||
487 | return mHtmlWriter; | ||
488 | } | ||
489 | return mSource->htmlWriter(); | ||
490 | } | ||
491 | |||
492 | MimeTreeParser::NodeHelper *ObjectTreeParser::nodeHelper() const | ||
493 | { | ||
494 | return mNodeHelper; | ||
495 | } | ||