summaryrefslogtreecommitdiffstats
path: root/framework/src/domain/mime/mimetreeparser/objecttreeparser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/domain/mime/mimetreeparser/objecttreeparser.cpp')
-rw-r--r--framework/src/domain/mime/mimetreeparser/objecttreeparser.cpp549
1 files changed, 549 insertions, 0 deletions
diff --git a/framework/src/domain/mime/mimetreeparser/objecttreeparser.cpp b/framework/src/domain/mime/mimetreeparser/objecttreeparser.cpp
new file mode 100644
index 00000000..914298b9
--- /dev/null
+++ b/framework/src/domain/mime/mimetreeparser/objecttreeparser.cpp
@@ -0,0 +1,549 @@
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 "bodypartformatterbasefactory.h"
40#include "nodehelper.h"
41#include "messagepart.h"
42#include "partnodebodypart.h"
43
44#include "mimetreeparser_debug.h"
45
46#include "utils.h"
47#include "bodypartformatter.h"
48#include "util.h"
49
50#include <KMime/Headers>
51#include <KMime/Message>
52
53// KDE includes
54
55// Qt includes
56#include <QByteArray>
57#include <QTextCodec>
58#include <QUrl>
59#include <QMimeDatabase>
60
61using namespace MimeTreeParser;
62
63
64ObjectTreeParser::ObjectTreeParser()
65 : mNodeHelper(nullptr),
66 mTopLevelContent(nullptr),
67 mShowOnlyOneMimePart(false),
68 mHasPendingAsyncJobs(false),
69 mAllowAsync(false)
70{
71 init();
72}
73
74ObjectTreeParser::ObjectTreeParser(MimeTreeParser::NodeHelper *nodeHelper,
75 bool showOnlyOneMimePart)
76 : mNodeHelper(nodeHelper),
77 mTopLevelContent(nullptr),
78 mShowOnlyOneMimePart(showOnlyOneMimePart),
79 mHasPendingAsyncJobs(false),
80 mAllowAsync(false)
81{
82 init();
83}
84
85void ObjectTreeParser::init()
86{
87 if (!mNodeHelper) {
88 mNodeHelper = new NodeHelper();
89 mDeleteNodeHelper = true;
90 } else {
91 mDeleteNodeHelper = false;
92 }
93}
94
95ObjectTreeParser::~ObjectTreeParser()
96{
97 if (mDeleteNodeHelper) {
98 delete mNodeHelper;
99 mNodeHelper = nullptr;
100 }
101}
102
103void ObjectTreeParser::setAllowAsync(bool allow)
104{
105 Q_ASSERT(!mHasPendingAsyncJobs);
106 mAllowAsync = allow;
107}
108
109bool ObjectTreeParser::allowAsync() const
110{
111 return mAllowAsync;
112}
113
114bool ObjectTreeParser::hasPendingAsyncJobs() const
115{
116 return mHasPendingAsyncJobs;
117}
118
119QString ObjectTreeParser::plainTextContent() const
120{
121 return mPlainTextContent;
122}
123
124QString ObjectTreeParser::htmlContent() const
125{
126 return mHtmlContent;
127}
128
129static void print(KMime::Content *node, const QString prefix = {})
130{
131 QByteArray mediaType("text");
132 QByteArray subType("plain");
133 if (node->contentType(false) && !node->contentType()->mediaType().isEmpty() &&
134 !node->contentType()->subType().isEmpty()) {
135 mediaType = node->contentType()->mediaType();
136 subType = node->contentType()->subType();
137 }
138 qWarning() << prefix << "!" << mediaType << subType;
139 for (const auto c: node->contents()) {
140 print(c, prefix + QLatin1String(" "));
141 }
142}
143
144static void print(const MessagePart &messagePart, const QByteArray pre = {})
145{
146 qWarning() << pre << "#" << messagePart.metaObject()->className();
147 for (const auto &p: messagePart.subParts()) {
148 print(*p, pre + " ");
149 }
150}
151
152void ObjectTreeParser::print()
153{
154 if (mTopLevelContent) {
155 ::print(mTopLevelContent);
156 }
157 if (mParsedPart) {
158 ::print(*mParsedPart);
159 }
160}
161
162static KMime::Content *find(KMime::Content *node, const std::function<bool(KMime::Content *)> &select)
163{
164 QByteArray mediaType("text");
165 QByteArray subType("plain");
166 if (node->contentType(false) && !node->contentType()->mediaType().isEmpty() &&
167 !node->contentType()->subType().isEmpty()) {
168 mediaType = node->contentType()->mediaType();
169 subType = node->contentType()->subType();
170 }
171 if (select(node)) {
172 return node;
173 }
174 for (const auto c: node->contents()) {
175 if (const auto n = find(c, select)) {
176 return n;
177 }
178 }
179 return nullptr;
180}
181
182
183KMime::Content *ObjectTreeParser::find(const std::function<bool(KMime::Content *)> &select)
184{
185 return ::find(mTopLevelContent, select);
186}
187
188/*
189 * Collect message parts bottom up.
190 * Filter to avoid evaluating a subtree.
191 * Select parts to include it in the result set. Selecting a part in a branch will keep any parent parts from being selected.
192 */
193static QVector<MessagePart::Ptr> collect(MessagePart::Ptr start, const std::function<bool(const MessagePartPtr &)> &filter, const std::function<bool(const MessagePartPtr &)> &select)
194{
195 MessagePartPtr ptr = start.dynamicCast<MessagePart>();
196 Q_ASSERT(ptr);
197 if (!filter(ptr)) {
198 return {};
199 }
200
201 QVector<MessagePart::Ptr> list;
202 if (ptr) {
203 for (const auto &p: ptr->subParts()) {
204 list << ::collect(p, filter, select);
205 }
206 }
207 //Don't consider this part if we already selected a subpart
208 if (list.isEmpty()) {
209 if (select(ptr)) {
210 list << start;
211 }
212 }
213 return list;
214}
215
216static bool isAttachment(MessagePart::Ptr part)
217{
218 //TODO
219 // show everything but the first text/plain body as attachment
220 if (part->disposition() == MessagePart::Inline) {
221 return false;
222 }
223 if (part->disposition() == MessagePart::Attachment) {
224 return true;
225 }
226 // text/* w/o filename parameter should go inline
227 if (part->node()) {
228 const auto ct = part->node()->contentType(false);
229 if (ct && ct->isText() && ct->name().trimmed().isEmpty() && part->filename().trimmed().isEmpty()) {
230 return false;
231 }
232 return true;
233 }
234 return false;
235}
236
237QVector<MessagePart::Ptr> ObjectTreeParser::collectContentParts()
238{
239 QVector<MessagePart::Ptr> contentParts = ::collect(mParsedPart,
240 [] (const MessagePartPtr &part) {
241 // return p->type() != "EncapsulatedPart";
242 return true;
243 },
244 [] (const MessagePartPtr &part) {
245 if (const auto attachment = dynamic_cast<MimeTreeParser::AttachmentMessagePart*>(part.data())) {
246 return false;
247 } else if (const auto text = dynamic_cast<MimeTreeParser::TextMessagePart*>(part.data())) {
248 auto enc = dynamic_cast<MimeTreeParser::EncryptedMessagePart*>(text->parentPart());
249 if (enc && enc->error()) {
250 return false;
251 }
252 return true;
253 } else if (const auto alternative = dynamic_cast<MimeTreeParser::AlternativeMessagePart*>(part.data())) {
254 return true;
255 } else if (const auto html = dynamic_cast<MimeTreeParser::HtmlMessagePart*>(part.data())) {
256 return true;
257 } else if (const auto enc = dynamic_cast<MimeTreeParser::EncryptedMessagePart*>(part.data())) {
258 if (enc->error()) {
259 return true;
260 }
261 //If we have a textpart with encrypted and unencrypted subparts we want to return the textpart
262 if (dynamic_cast<MimeTreeParser::TextMessagePart*>(enc->parentPart())) {
263 return false;
264 }
265 } else if (const auto sig = dynamic_cast<MimeTreeParser::SignedMessagePart*>(part.data())) {
266 //Signatures without subparts already contain the text
267 return !sig->hasSubParts();
268 }
269 return false;
270 });
271 return contentParts;
272}
273
274QVector<MessagePart::Ptr> ObjectTreeParser::collectAttachmentParts()
275{
276 QVector<MessagePart::Ptr> contentParts = ::collect(mParsedPart,
277 [] (const MessagePartPtr &part) {
278 return true;
279 },
280 [] (const MessagePartPtr &part) {
281 if (const auto attachment = dynamic_cast<MimeTreeParser::AttachmentMessagePart*>(part.data())) {
282 return true;
283 }
284 return false;
285 });
286 return contentParts;
287}
288
289void ObjectTreeParser::decryptParts()
290{
291 ::collect(mParsedPart,
292 [] (const MessagePartPtr &part) { return true; },
293 [] (const MessagePartPtr &part) {
294 if (const auto enc = dynamic_cast<MimeTreeParser::EncryptedMessagePart*>(part.data())) {
295 enc->startDecryption();
296 }
297 return false;
298 });
299 print();
300 ::collect(mParsedPart,
301 [] (const MessagePartPtr &part) { return true; },
302 [] (const MessagePartPtr &part) {
303 if (const auto enc = dynamic_cast<MimeTreeParser::SignedMessagePart*>(part.data())) {
304 enc->startVerification();
305 }
306 return false;
307 });
308}
309
310void ObjectTreeParser::importCertificates()
311{
312 QVector<MessagePart::Ptr> contentParts = ::collect(mParsedPart,
313 [] (const MessagePartPtr &part) { return true; },
314 [] (const MessagePartPtr &part) {
315 if (const auto cert = dynamic_cast<MimeTreeParser::CertMessagePart*>(part.data())) {
316 cert->import();
317 }
318 return false;
319 });
320}
321
322
323QString ObjectTreeParser::resolveCidLinks(const QString &html)
324{
325 auto text = html;
326 const auto rx = QRegExp(QLatin1String("(src)\\s*=\\s*(\"|')(cid:[^\"']+)\\2"));
327 int pos = 0;
328 while ((pos = rx.indexIn(text, pos)) != -1) {
329 const auto link = QUrl(rx.cap(3));
330 pos += rx.matchedLength();
331 auto cid = link.path();
332 auto mailMime = const_cast<KMime::Content *>(find([=] (KMime::Content *c) {
333 if (!c || !c->contentID(false)) {
334 return false;
335 }
336 return QString::fromLatin1(c->contentID(false)->identifier()) == cid;
337 }));
338 if (mailMime) {
339 const auto ct = mailMime->contentType(false);
340 if (!ct) {
341 qWarning() << "No content type, skipping";
342 continue;
343 }
344 QMimeDatabase mimeDb;
345 const auto mimetype = mimeDb.mimeTypeForName(QString::fromLatin1(ct->mimeType())).name();
346 if (mimetype.startsWith(QLatin1String("image/"))) {
347 //We reencode to base64 below.
348 const auto data = mailMime->decodedContent();
349 if (data.isEmpty()) {
350 qWarning() << "Attachment is empty.";
351 continue;
352 }
353 text.replace(rx.cap(0), QString::fromLatin1("src=\"data:%1;base64,%2\"").arg(mimetype, QString::fromLatin1(data.toBase64())));
354 }
355 } else {
356 qWarning() << "Failed to find referenced attachment: " << cid;
357 }
358 }
359 return text;
360}
361
362//-----------------------------------------------------------------------------
363
364void ObjectTreeParser::parseObjectTree(const QByteArray &mimeMessage)
365{
366 const auto mailData = KMime::CRLFtoLF(mimeMessage);
367 mMsg = KMime::Message::Ptr(new KMime::Message);
368 mMsg->setContent(mailData);
369 mMsg->parse();
370 parseObjectTree(mMsg.data());
371}
372
373void ObjectTreeParser::parseObjectTree(KMime::Content *node)
374{
375 mTopLevelContent = node;
376 mParsedPart = parseObjectTreeInternal(node, showOnlyOneMimePart());
377
378 //Gather plaintext and html content
379 if (mParsedPart) {
380 //Find relevant plaintext parts and set plaintext
381 if (auto mp = toplevelTextNode(mParsedPart)) {
382 if (auto _mp = mp.dynamicCast<TextMessagePart>()) {
383 mPlainTextContent += _mp->mNode->decodedText();
384 mPlainTextContentCharset += NodeHelper::charset(_mp->mNode);
385 } else if (auto _mp = mp.dynamicCast<AlternativeMessagePart>()) {
386 if (_mp->mChildNodes.contains(Util::MultipartPlain)) {
387 mPlainTextContent += _mp->mChildNodes[Util::MultipartPlain]->decodedText();
388 mPlainTextContentCharset += NodeHelper::charset(_mp->mChildNodes[Util::MultipartPlain]);
389 }
390 }
391 }
392
393 //Find html parts and copy content
394 QVector<MessagePart::Ptr> contentParts = ::collect(mParsedPart,
395 [] (const MessagePartPtr &part) {
396 return true;
397 },
398 [] (const MessagePartPtr &part) {
399 if (const auto html = dynamic_cast<MimeTreeParser::HtmlMessagePart*>(part.data())) {
400 return true;
401 }
402 return false;
403 });
404 for (const auto &part : contentParts) {
405 mHtmlContent += part->text();
406 mHtmlContentCharset = part->charset();
407 }
408 }
409}
410
411MessagePartPtr ObjectTreeParser::parsedPart() const
412{
413 return mParsedPart;
414}
415
416MessagePartPtr ObjectTreeParser::processType(KMime::Content *node, const QByteArray &mediaType, const QByteArray &subType, bool onlyOneMimePart)
417{
418 static MimeTreeParser::BodyPartFormatterBaseFactory factory;
419 const auto sub = factory.subtypeRegistry(mediaType.constData());
420 auto range = sub.equal_range(subType.constData());
421 for (auto it = range.first; it != range.second; ++it) {
422 const auto formatter = (*it).second;
423 if (!formatter) {
424 continue;
425 }
426 PartNodeBodyPart part(this, mTopLevelContent, node, mNodeHelper);
427 if (const MessagePart::Ptr result = formatter->process(part)) {
428 return result;
429 }
430 }
431 return {};
432}
433
434MessagePart::Ptr ObjectTreeParser::parseObjectTreeInternal(KMime::Content *node, bool onlyOneMimePart)
435{
436 if (!node) {
437 return MessagePart::Ptr();
438 }
439
440 // reset pending async jobs state (we'll rediscover pending jobs as we go)
441 mHasPendingAsyncJobs = false;
442
443 // reset "processed" flags for...
444 if (onlyOneMimePart) {
445 // ... this node and all descendants
446 mNodeHelper->setNodeUnprocessed(node, false);
447 if (!node->contents().isEmpty()) {
448 mNodeHelper->setNodeUnprocessed(node, true);
449 }
450 } else if (!node->parent()) {
451 // ...this node and all it's siblings and descendants
452 mNodeHelper->setNodeUnprocessed(node, true);
453 }
454
455 const bool isRoot = node->isTopLevel();
456 auto parsedPart = MessagePart::Ptr(new MessagePartList(this));
457 parsedPart->setIsRoot(isRoot);
458 KMime::Content *parent = node->parent();
459 auto contents = parent ? parent->contents() : KMime::Content::List();
460 if (contents.isEmpty()) {
461 contents.append(node);
462 }
463 int i = contents.indexOf(const_cast<KMime::Content *>(node));
464 for (; i < contents.size(); ++i) {
465 node = contents.at(i);
466 if (mNodeHelper->nodeProcessed(node)) {
467 continue;
468 }
469
470 QByteArray mediaType("text");
471 QByteArray subType("plain");
472 if (node->contentType(false) && !node->contentType()->mediaType().isEmpty() &&
473 !node->contentType()->subType().isEmpty()) {
474 mediaType = node->contentType()->mediaType();
475 subType = node->contentType()->subType();
476 }
477
478 //Try the specific type handler
479 if (auto mp = processType(node, mediaType, subType, onlyOneMimePart)) {
480 if (mp) {
481 parsedPart->appendSubPart(mp);
482 }
483 //Fallback to the generic handler
484 } else if (auto mp = processType(node, mediaType, "*", onlyOneMimePart)) {
485 if (mp) {
486 parsedPart->appendSubPart(mp);
487 }
488 //Fallback to the default handler
489 } else {
490 if (auto mp = defaultHandling(node, onlyOneMimePart)) {
491 parsedPart->appendSubPart(mp);
492 }
493 }
494 mNodeHelper->setNodeProcessed(node, false);
495
496 if (onlyOneMimePart) {
497 break;
498 }
499 }
500
501 return parsedPart;
502}
503
504MessagePart::Ptr ObjectTreeParser::defaultHandling(KMime::Content *node, bool onlyOneMimePart)
505{
506 if (node->contentType()->mimeType() == QByteArrayLiteral("application/octet-stream") &&
507 (node->contentType()->name().endsWith(QLatin1String("p7m")) ||
508 node->contentType()->name().endsWith(QLatin1String("p7s")) ||
509 node->contentType()->name().endsWith(QLatin1String("p7c"))
510 )) {
511 if (auto mp = processType(node, "application", "pkcs7-mime", onlyOneMimePart)) {
512 return mp;
513 }
514 }
515
516 const auto mp = AttachmentMessagePart::Ptr(new AttachmentMessagePart(this, node));
517 return mp;
518}
519
520const QTextCodec *ObjectTreeParser::codecFor(KMime::Content *node) const
521{
522 Q_ASSERT(node);
523 return mNodeHelper->codec(node);
524}
525
526QByteArray ObjectTreeParser::plainTextContentCharset() const
527{
528 return mPlainTextContentCharset;
529}
530
531QByteArray ObjectTreeParser::htmlContentCharset() const
532{
533 return mHtmlContentCharset;
534}
535
536bool ObjectTreeParser::showOnlyOneMimePart() const
537{
538 return mShowOnlyOneMimePart;
539}
540
541void ObjectTreeParser::setShowOnlyOneMimePart(bool show)
542{
543 mShowOnlyOneMimePart = show;
544}
545
546MimeTreeParser::NodeHelper *ObjectTreeParser::nodeHelper() const
547{
548 return mNodeHelper;
549}