summaryrefslogtreecommitdiffstats
path: root/framework/src/domain/mimetreeparser/otp/nodehelper.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/domain/mimetreeparser/otp/nodehelper.cpp')
-rw-r--r--framework/src/domain/mimetreeparser/otp/nodehelper.cpp1069
1 files changed, 1069 insertions, 0 deletions
diff --git a/framework/src/domain/mimetreeparser/otp/nodehelper.cpp b/framework/src/domain/mimetreeparser/otp/nodehelper.cpp
new file mode 100644
index 00000000..8e224f1b
--- /dev/null
+++ b/framework/src/domain/mimetreeparser/otp/nodehelper.cpp
@@ -0,0 +1,1069 @@
1/*
2 Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
3 Copyright (c) 2009 Andras Mantia <andras@kdab.net>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18*/
19
20#include "nodehelper.h"
21#include "mimetreeparser_debug.h"
22#include "partmetadata.h"
23#include "bodypart.h"
24#include "attachmenttemporaryfilesdirs.h"
25
26#include <KMime/Content>
27#include <KMime/Message>
28#include <KMime/Headers>
29
30#include <QTemporaryFile>
31#include <KLocalizedString>
32#include <kcharsets.h>
33
34#include <QUrl>
35#include <QDir>
36#include <QTextCodec>
37
38#include <string>
39#include <sstream>
40#include <algorithm>
41#include <KCharsets>
42#include <QMimeDatabase>
43#include <QMimeType>
44#include <QFileDevice>
45
46namespace MimeTreeParser
47{
48
49QStringList replySubjPrefixes(QStringList() << QStringLiteral("Re\\s*:") << QStringLiteral("Re\\[\\d+\\]:") << QStringLiteral("Re\\d+:"));
50QStringList forwardSubjPrefixes(QStringList() << QStringLiteral("Fwd:") << QStringLiteral("FW:"));
51
52NodeHelper::NodeHelper() :
53 mAttachmentFilesDir(new AttachmentTemporaryFilesDirs())
54{
55 //TODO(Andras) add methods to modify these prefixes
56
57 mLocalCodec = QTextCodec::codecForLocale();
58
59 // In the case of Japan. Japanese locale name is "eucjp" but
60 // The Japanese mail systems normally used "iso-2022-jp" of locale name.
61 // We want to change locale name from eucjp to iso-2022-jp at KMail only.
62
63 // (Introduction to i18n, 6.6 Limit of Locale technology):
64 // EUC-JP is the de-facto standard for UNIX systems, ISO 2022-JP
65 // is the standard for Internet, and Shift-JIS is the encoding
66 // for Windows and Macintosh.
67 if (mLocalCodec) {
68 const QByteArray codecNameLower = mLocalCodec->name().toLower();
69 if (codecNameLower == "eucjp"
70#if defined Q_OS_WIN || defined Q_OS_MACX
71 || codecNameLower == "shift-jis" // OK?
72#endif
73 ) {
74 mLocalCodec = QTextCodec::codecForName("jis7");
75 // QTextCodec *cdc = QTextCodec::codecForName("jis7");
76 // QTextCodec::setCodecForLocale(cdc);
77 // KLocale::global()->setEncoding(cdc->mibEnum());
78 }
79 }
80}
81
82NodeHelper::~NodeHelper()
83{
84 if (mAttachmentFilesDir) {
85 mAttachmentFilesDir->forceCleanTempFiles();
86 delete mAttachmentFilesDir;
87 mAttachmentFilesDir = nullptr;
88 }
89 clear();
90}
91
92void NodeHelper::setNodeProcessed(KMime::Content *node, bool recurse)
93{
94 if (!node) {
95 return;
96 }
97 mProcessedNodes.append(node);
98 qCDebug(MIMETREEPARSER_LOG) << "Node processed: " << node->index().toString() << node->contentType()->as7BitString();
99 //<< " decodedContent" << node->decodedContent();
100 if (recurse) {
101 const auto contents = node->contents();
102 for (KMime::Content *c : contents) {
103 setNodeProcessed(c, true);
104 }
105 }
106}
107
108void NodeHelper::setNodeUnprocessed(KMime::Content *node, bool recurse)
109{
110 if (!node) {
111 return;
112 }
113 mProcessedNodes.removeAll(node);
114
115 //avoid double addition of extra nodes, eg. encrypted attachments
116 const QMap<KMime::Content *, QList<KMime::Content *> >::iterator it = mExtraContents.find(node);
117 if (it != mExtraContents.end()) {
118 Q_FOREACH (KMime::Content *c, it.value()) {
119 KMime::Content *p = c->parent();
120 if (p) {
121 p->removeContent(c);
122 }
123 }
124 qDeleteAll(it.value());
125 qCDebug(MIMETREEPARSER_LOG) << "mExtraContents deleted for" << it.key();
126 mExtraContents.erase(it);
127 }
128
129 qCDebug(MIMETREEPARSER_LOG) << "Node UNprocessed: " << node;
130 if (recurse) {
131 const auto contents = node->contents();
132 for (KMime::Content *c : contents) {
133 setNodeUnprocessed(c, true);
134 }
135 }
136}
137
138bool NodeHelper::nodeProcessed(KMime::Content *node) const
139{
140 if (!node) {
141 return true;
142 }
143 return mProcessedNodes.contains(node);
144}
145
146static void clearBodyPartMemento(QMap<QByteArray, Interface::BodyPartMemento *> &bodyPartMementoMap)
147{
148 for (QMap<QByteArray, Interface::BodyPartMemento *>::iterator
149 it = bodyPartMementoMap.begin(), end = bodyPartMementoMap.end();
150 it != end; ++it) {
151 Interface::BodyPartMemento *memento = it.value();
152 memento->detach();
153 delete memento;
154 }
155 bodyPartMementoMap.clear();
156}
157
158void NodeHelper::clear()
159{
160 mProcessedNodes.clear();
161 mEncryptionState.clear();
162 mSignatureState.clear();
163 mOverrideCodecs.clear();
164 std::for_each(mBodyPartMementoMap.begin(), mBodyPartMementoMap.end(),
165 &clearBodyPartMemento);
166 mBodyPartMementoMap.clear();
167 QMap<KMime::Content *, QList<KMime::Content *> >::ConstIterator end(mExtraContents.constEnd());
168
169 for (QMap<KMime::Content *, QList<KMime::Content *> >::ConstIterator it = mExtraContents.constBegin(); it != end; ++it) {
170 Q_FOREACH (KMime::Content *c, it.value()) {
171 KMime::Content *p = c->parent();
172 if (p) {
173 p->removeContent(c);
174 }
175 }
176 qDeleteAll(it.value());
177 qCDebug(MIMETREEPARSER_LOG) << "mExtraContents deleted for" << it.key();
178 }
179 mExtraContents.clear();
180 mDisplayEmbeddedNodes.clear();
181 mDisplayHiddenNodes.clear();
182}
183
184void NodeHelper::setEncryptionState(const KMime::Content *node, const KMMsgEncryptionState state)
185{
186 mEncryptionState[node] = state;
187}
188
189KMMsgEncryptionState NodeHelper::encryptionState(const KMime::Content *node) const
190{
191 return mEncryptionState.value(node, KMMsgNotEncrypted);
192}
193
194void NodeHelper::setSignatureState(const KMime::Content *node, const KMMsgSignatureState state)
195{
196 mSignatureState[node] = state;
197}
198
199KMMsgSignatureState NodeHelper::signatureState(const KMime::Content *node) const
200{
201 return mSignatureState.value(node, KMMsgNotSigned);
202}
203
204PartMetaData NodeHelper::partMetaData(KMime::Content *node)
205{
206 return mPartMetaDatas.value(node, PartMetaData());
207}
208
209void NodeHelper::setPartMetaData(KMime::Content *node, const PartMetaData &metaData)
210{
211 mPartMetaDatas.insert(node, metaData);
212}
213
214QString NodeHelper::writeNodeToTempFile(KMime::Content *node)
215{
216 // If the message part is already written to a file, no point in doing it again.
217 // This function is called twice actually, once from the rendering of the attachment
218 // in the body and once for the header.
219 QUrl existingFileName = tempFileUrlFromNode(node);
220 if (!existingFileName.isEmpty()) {
221 return existingFileName.toLocalFile();
222 }
223
224 QString fname = createTempDir(persistentIndex(node));
225 if (fname.isEmpty()) {
226 return QString();
227 }
228
229 QString fileName = NodeHelper::fileName(node);
230 // strip off a leading path
231 int slashPos = fileName.lastIndexOf(QLatin1Char('/'));
232 if (-1 != slashPos) {
233 fileName = fileName.mid(slashPos + 1);
234 }
235 if (fileName.isEmpty()) {
236 fileName = QStringLiteral("unnamed");
237 }
238 fname += QLatin1Char('/') + fileName;
239
240 qCDebug(MIMETREEPARSER_LOG) << "Create temp file: " << fname;
241 QByteArray data = node->decodedContent();
242 if (node->contentType()->isText() && !data.isEmpty()) {
243 // convert CRLF to LF before writing text attachments to disk
244 data = KMime::CRLFtoLF(data);
245 }
246 QFile f(fname);
247 if (!f.open(QIODevice::ReadWrite)) {
248 qCWarning(MIMETREEPARSER_LOG) << "Failed to write note to file:" << f.errorString();
249 return QString();
250 }
251 f.write(data);
252 mAttachmentFilesDir->addTempFile(fname);
253 // make file read-only so that nobody gets the impression that he might
254 // edit attached files (cf. bug #52813)
255 f.setPermissions(QFileDevice::ReadUser);
256 f.close();
257
258 return fname;
259}
260
261QUrl NodeHelper::tempFileUrlFromNode(const KMime::Content *node)
262{
263 if (!node) {
264 return QUrl();
265 }
266
267 const QString index = persistentIndex(node);
268
269 foreach (const QString &path, mAttachmentFilesDir->temporaryFiles()) {
270 const int right = path.lastIndexOf(QLatin1Char('/'));
271 int left = path.lastIndexOf(QLatin1String(".index."), right);
272 if (left != -1) {
273 left += 7;
274 }
275
276 QStringRef storedIndex(&path, left, right - left);
277 if (left != -1 && storedIndex == index) {
278 return QUrl::fromLocalFile(path);
279 }
280 }
281 return QUrl();
282}
283
284QString NodeHelper::createTempDir(const QString &param)
285{
286 QTemporaryFile *tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/messageviewer_XXXXXX") + QLatin1String(".index.") + param);
287 tempFile->open();
288 const QString fname = tempFile->fileName();
289 delete tempFile;
290
291 QFile fFile(fname);
292 if (!(fFile.permissions() & QFileDevice::WriteUser)) {
293 // Not there or not writable
294 if (!QDir().mkpath(fname) ||
295 !fFile.setPermissions(QFileDevice::WriteUser | QFileDevice::ReadUser | QFileDevice::ExeUser)) {
296 return QString(); //failed create
297 }
298 }
299
300 Q_ASSERT(!fname.isNull());
301
302 mAttachmentFilesDir->addTempDir(fname);
303 return fname;
304}
305
306void NodeHelper::forceCleanTempFiles()
307{
308 mAttachmentFilesDir->forceCleanTempFiles();
309 delete mAttachmentFilesDir;
310 mAttachmentFilesDir = nullptr;
311}
312
313void NodeHelper::removeTempFiles()
314{
315 //Don't delete it it will delete in class
316 mAttachmentFilesDir->removeTempFiles();
317 mAttachmentFilesDir = new AttachmentTemporaryFilesDirs();
318}
319
320void NodeHelper::addTempFile(const QString &file)
321{
322 mAttachmentFilesDir->addTempFile(file);
323}
324
325bool NodeHelper::isInEncapsulatedMessage(KMime::Content *node)
326{
327 const KMime::Content *const topLevel = node->topLevel();
328 const KMime::Content *cur = node;
329 while (cur && cur != topLevel) {
330 const bool parentIsMessage = cur->parent() && cur->parent()->contentType(false) &&
331 cur->parent()->contentType()->mimeType().toLower() == "message/rfc822";
332 if (parentIsMessage && cur->parent() != topLevel) {
333 return true;
334 }
335 cur = cur->parent();
336 }
337 return false;
338}
339
340QByteArray NodeHelper::charset(KMime::Content *node)
341{
342 if (node->contentType(false)) {
343 return node->contentType(false)->charset();
344 } else {
345 return node->defaultCharset();
346 }
347}
348
349KMMsgEncryptionState NodeHelper::overallEncryptionState(KMime::Content *node) const
350{
351 KMMsgEncryptionState myState = KMMsgEncryptionStateUnknown;
352 if (!node) {
353 return myState;
354 }
355
356 KMime::Content *parent = node->parent();
357 auto contents = parent ? parent->contents() : KMime::Content::List();
358 if (contents.isEmpty()) {
359 contents.append(node);
360 }
361 int i = contents.indexOf(const_cast<KMime::Content *>(node));
362 for (; i < contents.size(); ++i) {
363 auto next = contents.at(i);
364 KMMsgEncryptionState otherState = encryptionState(next);
365
366 // NOTE: children are tested ONLY when parent is not encrypted
367 if (otherState == KMMsgNotEncrypted && !next->contents().isEmpty()) {
368 otherState = overallEncryptionState(next->contents().at(0));
369 }
370
371 if (otherState == KMMsgNotEncrypted && !extraContents(next).isEmpty()) {
372 otherState = overallEncryptionState(extraContents(next).at(0));
373 }
374
375 if (next == node) {
376 myState = otherState;
377 }
378
379 switch (otherState) {
380 case KMMsgEncryptionStateUnknown:
381 break;
382 case KMMsgNotEncrypted:
383 if (myState == KMMsgFullyEncrypted) {
384 myState = KMMsgPartiallyEncrypted;
385 } else if (myState != KMMsgPartiallyEncrypted) {
386 myState = KMMsgNotEncrypted;
387 }
388 break;
389 case KMMsgPartiallyEncrypted:
390 myState = KMMsgPartiallyEncrypted;
391 break;
392 case KMMsgFullyEncrypted:
393 if (myState != KMMsgFullyEncrypted) {
394 myState = KMMsgPartiallyEncrypted;
395 }
396 break;
397 case KMMsgEncryptionProblematic:
398 break;
399 }
400 }
401
402 qCDebug(MIMETREEPARSER_LOG) << "\n\n KMMsgEncryptionState:" << myState;
403
404 return myState;
405}
406
407KMMsgSignatureState NodeHelper::overallSignatureState(KMime::Content *node) const
408{
409 KMMsgSignatureState myState = KMMsgSignatureStateUnknown;
410 if (!node) {
411 return myState;
412 }
413
414 KMime::Content *parent = node->parent();
415 auto contents = parent ? parent->contents() : KMime::Content::List();
416 if (contents.isEmpty()) {
417 contents.append(node);
418 }
419 int i = contents.indexOf(const_cast<KMime::Content *>(node));
420 for (; i < contents.size(); ++i) {
421 auto next = contents.at(i);
422 KMMsgSignatureState otherState = signatureState(next);
423
424 // NOTE: children are tested ONLY when parent is not encrypted
425 if (otherState == KMMsgNotSigned && !next->contents().isEmpty()) {
426 otherState = overallSignatureState(next->contents().at(0));
427 }
428
429 if (otherState == KMMsgNotSigned && !extraContents(next).isEmpty()) {
430 otherState = overallSignatureState(extraContents(next).at(0));
431 }
432
433 if (next == node) {
434 myState = otherState;
435 }
436
437 switch (otherState) {
438 case KMMsgSignatureStateUnknown:
439 break;
440 case KMMsgNotSigned:
441 if (myState == KMMsgFullySigned) {
442 myState = KMMsgPartiallySigned;
443 } else if (myState != KMMsgPartiallySigned) {
444 myState = KMMsgNotSigned;
445 }
446 break;
447 case KMMsgPartiallySigned:
448 myState = KMMsgPartiallySigned;
449 break;
450 case KMMsgFullySigned:
451 if (myState != KMMsgFullySigned) {
452 myState = KMMsgPartiallySigned;
453 }
454 break;
455 case KMMsgSignatureProblematic:
456 break;
457 }
458 }
459
460 qCDebug(MIMETREEPARSER_LOG) << "\n\n KMMsgSignatureState:" << myState;
461
462 return myState;
463}
464
465void NodeHelper::magicSetType(KMime::Content *node, bool aAutoDecode)
466{
467 const QByteArray body = (aAutoDecode) ? node->decodedContent() : node->body();
468 QMimeDatabase db;
469 QMimeType mime = db.mimeTypeForData(body);
470
471 QString mimetype = mime.name();
472 node->contentType()->setMimeType(mimetype.toLatin1());
473}
474
475// static
476QString NodeHelper::replacePrefixes(const QString &str,
477 const QStringList &prefixRegExps,
478 bool replace,
479 const QString &newPrefix)
480{
481 bool recognized = false;
482 // construct a big regexp that
483 // 1. is anchored to the beginning of str (sans whitespace)
484 // 2. matches at least one of the part regexps in prefixRegExps
485 QString bigRegExp = QStringLiteral("^(?:\\s+|(?:%1))+\\s*")
486 .arg(prefixRegExps.join(QStringLiteral(")|(?:")));
487 QRegExp rx(bigRegExp, Qt::CaseInsensitive);
488 if (!rx.isValid()) {
489 qCWarning(MIMETREEPARSER_LOG) << "bigRegExp = \""
490 << bigRegExp << "\"\n"
491 << "prefix regexp is invalid!";
492 // try good ole Re/Fwd:
493 recognized = str.startsWith(newPrefix);
494 } else { // valid rx
495 QString tmp = str;
496 if (rx.indexIn(tmp) == 0) {
497 recognized = true;
498 if (replace) {
499 return tmp.replace(0, rx.matchedLength(), newPrefix + QLatin1Char(' '));
500 }
501 }
502 }
503 if (!recognized) {
504 return newPrefix + QLatin1Char(' ') + str;
505 } else {
506 return str;
507 }
508}
509
510QString NodeHelper::cleanSubject(KMime::Message *message)
511{
512 return cleanSubject(message, replySubjPrefixes + forwardSubjPrefixes,
513 true, QString()).trimmed();
514}
515
516QString NodeHelper::cleanSubject(KMime::Message *message,
517 const QStringList &prefixRegExps,
518 bool replace,
519 const QString &newPrefix)
520{
521 QString cleanStr;
522 if (message) {
523 cleanStr =
524 NodeHelper::replacePrefixes(
525 message->subject()->asUnicodeString(), prefixRegExps, replace, newPrefix);
526 }
527 return cleanStr;
528}
529
530void NodeHelper::setOverrideCodec(KMime::Content *node, const QTextCodec *codec)
531{
532 if (!node) {
533 return;
534 }
535
536 mOverrideCodecs[node] = codec;
537}
538
539const QTextCodec *NodeHelper::codec(KMime::Content *node)
540{
541 if (! node) {
542 return mLocalCodec;
543 }
544
545 const QTextCodec *c = mOverrideCodecs.value(node, nullptr);
546 if (!c) {
547 // no override-codec set for this message, try the CT charset parameter:
548 QByteArray charset = node->contentType()->charset();
549
550 // utf-8 is a superset of us-ascii, so we don't loose anything, if we it insead
551 // utf-8 is nowadays that widely, that it is a good guess to use it to fix issus with broken clients.
552 if (charset.toLower() == "us-ascii") {
553 charset = "utf-8";
554 }
555 c = codecForName(charset);
556 }
557 if (!c) {
558 // no charset means us-ascii (RFC 2045), so using local encoding should
559 // be okay
560 c = mLocalCodec;
561 }
562 return c;
563}
564
565const QTextCodec *NodeHelper::codecForName(const QByteArray &_str)
566{
567 if (_str.isEmpty()) {
568 return nullptr;
569 }
570 QByteArray codec = _str.toLower();
571 return KCharsets::charsets()->codecForName(QLatin1String(codec));
572}
573
574QString NodeHelper::fileName(const KMime::Content *node)
575{
576 QString name = const_cast<KMime::Content *>(node)->contentDisposition()->filename();
577 if (name.isEmpty()) {
578 name = const_cast<KMime::Content *>(node)->contentType()->name();
579 }
580
581 name = name.trimmed();
582 return name;
583}
584
585//FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalBodyPartMemento replacement
586Interface::BodyPartMemento *NodeHelper::bodyPartMemento(KMime::Content *node,
587 const QByteArray &which) const
588{
589 const QMap< QString, QMap<QByteArray, Interface::BodyPartMemento *> >::const_iterator nit
590 = mBodyPartMementoMap.find(persistentIndex(node));
591 if (nit == mBodyPartMementoMap.end()) {
592 return nullptr;
593 }
594 const QMap<QByteArray, Interface::BodyPartMemento *>::const_iterator it =
595 nit->find(which.toLower());
596 return it != nit->end() ? it.value() : nullptr;
597}
598
599//FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalSetBodyPartMemento replacement
600void NodeHelper::setBodyPartMemento(KMime::Content *node, const QByteArray &which,
601 Interface::BodyPartMemento *memento)
602{
603 QMap<QByteArray, Interface::BodyPartMemento *> &mementos
604 = mBodyPartMementoMap[persistentIndex(node)];
605
606 const QByteArray whichLower = which.toLower();
607 const QMap<QByteArray, Interface::BodyPartMemento *>::iterator it =
608 mementos.lowerBound(whichLower);
609
610 if (it != mementos.end() && it.key() == whichLower) {
611 delete it.value();
612 if (memento) {
613 it.value() = memento;
614 } else {
615 mementos.erase(it);
616 }
617 } else {
618 mementos.insert(whichLower, memento);
619 }
620}
621
622bool NodeHelper::isNodeDisplayedEmbedded(KMime::Content *node) const
623{
624 qCDebug(MIMETREEPARSER_LOG) << "IS NODE: " << mDisplayEmbeddedNodes.contains(node);
625 return mDisplayEmbeddedNodes.contains(node);
626}
627
628void NodeHelper::setNodeDisplayedEmbedded(KMime::Content *node, bool displayedEmbedded)
629{
630 qCDebug(MIMETREEPARSER_LOG) << "SET NODE: " << node << displayedEmbedded;
631 if (displayedEmbedded) {
632 mDisplayEmbeddedNodes.insert(node);
633 } else {
634 mDisplayEmbeddedNodes.remove(node);
635 }
636}
637
638bool NodeHelper::isNodeDisplayedHidden(KMime::Content *node) const
639{
640 return mDisplayHiddenNodes.contains(node);
641}
642
643void NodeHelper::setNodeDisplayedHidden(KMime::Content *node, bool displayedHidden)
644{
645 if (displayedHidden) {
646 mDisplayHiddenNodes.insert(node);
647 } else {
648 mDisplayEmbeddedNodes.remove(node);
649 }
650}
651
652/*!
653 Creates a persistent index string that bridges the gap between the
654 permanent nodes and the temporary ones.
655
656 Used internally for robust indexing.
657*/
658QString NodeHelper::persistentIndex(const KMime::Content *node) const
659{
660 if (!node) {
661 return QString();
662 }
663
664 QString indexStr = node->index().toString();
665 if (indexStr.isEmpty()) {
666 QMapIterator<KMime::Message::Content *, QList<KMime::Content *> > it(mExtraContents);
667 while (it.hasNext()) {
668 it.next();
669 const auto &extraNodes = it.value();
670 for (int i = 0; i < extraNodes.size(); i++) {
671 if (extraNodes[i] == node) {
672 indexStr = QString::fromLatin1("e%1").arg(i);
673 const QString parentIndex = persistentIndex(it.key());
674 if (!parentIndex.isEmpty()) {
675 indexStr = QString::fromLatin1("%1:%2").arg(parentIndex, indexStr);
676 }
677 return indexStr;
678 }
679 }
680 }
681 } else {
682 const KMime::Content *const topLevel = node->topLevel();
683 //if the node is an extra node, prepend the index of the extra node to the url
684 QMapIterator<KMime::Message::Content *, QList<KMime::Content *> > it(mExtraContents);
685 while (it.hasNext()) {
686 it.next();
687 const QList<KMime::Content *> &extraNodes = extraContents(it.key());
688 for (int i = 0; i < extraNodes.size(); ++i) {
689 KMime::Content *const extraNode = extraNodes[i];
690 if (topLevel == extraNode) {
691 indexStr.prepend(QStringLiteral("e%1:").arg(i));
692 const QString parentIndex = persistentIndex(it.key());
693 if (!parentIndex.isEmpty()) {
694 indexStr = QStringLiteral("%1:%2").arg(parentIndex, indexStr);
695 }
696 return indexStr;
697 }
698 }
699 }
700 }
701
702 return indexStr;
703}
704
705KMime::Content *NodeHelper::contentFromIndex(KMime::Content *node, const QString &persistentIndex) const
706{
707 KMime::Content *c = node->topLevel();
708 if (c) {
709 const QStringList pathParts = persistentIndex.split(QLatin1Char(':'), QString::SkipEmptyParts);
710 const int pathPartsSize(pathParts.size());
711 for (int i = 0; i < pathPartsSize; ++i) {
712 const QString &path = pathParts[i];
713 if (path.startsWith(QLatin1Char('e'))) {
714 const QList<KMime::Content *> &extraParts = mExtraContents.value(c);
715 const int idx = path.midRef(1, -1).toInt();
716 c = (idx < extraParts.size()) ? extraParts[idx] : nullptr;
717 } else {
718 c = c->content(KMime::ContentIndex(path));
719 }
720 if (!c) {
721 break;
722 }
723 }
724 }
725 return c;
726}
727
728QString NodeHelper::asHREF(const KMime::Content *node, const QString &place) const
729{
730 return QStringLiteral("attachment:%1?place=%2").arg(persistentIndex(node), place);
731}
732
733KMime::Content *NodeHelper::fromHREF(const KMime::Message::Ptr &mMessage, const QUrl &url) const
734{
735 if (url.isEmpty()) {
736 return mMessage.data();
737 }
738
739 if (!url.isLocalFile()) {
740 return contentFromIndex(mMessage.data(), url.adjusted(QUrl::StripTrailingSlash).path());
741 } else {
742 const QString path = url.toLocalFile();
743 // extract from /<path>/qttestn28554.index.2.3:0:2/unnamed -> "2.3:0:2"
744 // start of the index is something that is not a number followed by a dot: \D.
745 // index is only made of numbers,"." and ":": ([0-9.:]+)
746 // index is the last part of the folder name: /
747 const QRegExp rIndex(QStringLiteral("\\D\\.([e0-9.:]+)/"));
748
749 //search the occurence at most at the end
750 if (rIndex.lastIndexIn(path) != -1) {
751 return contentFromIndex(mMessage.data(), rIndex.cap(1));
752 }
753 return mMessage.data();
754 }
755}
756
757QString NodeHelper::fixEncoding(const QString &encoding)
758{
759 QString returnEncoding = encoding;
760 // According to http://www.iana.org/assignments/character-sets, uppercase is
761 // preferred in MIME headers
762 const QString returnEncodingToUpper = returnEncoding.toUpper();
763 if (returnEncodingToUpper.contains(QStringLiteral("ISO "))) {
764 returnEncoding = returnEncodingToUpper;
765 returnEncoding.replace(QLatin1String("ISO "), QStringLiteral("ISO-"));
766 }
767 return returnEncoding;
768}
769
770//-----------------------------------------------------------------------------
771QString NodeHelper::encodingForName(const QString &descriptiveName)
772{
773 QString encoding = KCharsets::charsets()->encodingForName(descriptiveName);
774 return NodeHelper::fixEncoding(encoding);
775}
776
777QStringList NodeHelper::supportedEncodings(bool usAscii)
778{
779 QStringList encodingNames = KCharsets::charsets()->availableEncodingNames();
780 QStringList encodings;
781 QMap<QString, bool> mimeNames;
782 QStringList::ConstIterator constEnd(encodingNames.constEnd());
783 for (QStringList::ConstIterator it = encodingNames.constBegin();
784 it != constEnd; ++it) {
785 QTextCodec *codec = KCharsets::charsets()->codecForName(*it);
786 QString mimeName = (codec) ? QString::fromLatin1(codec->name()).toLower() : (*it);
787 if (!mimeNames.contains(mimeName)) {
788 encodings.append(KCharsets::charsets()->descriptionForEncoding(*it));
789 mimeNames.insert(mimeName, true);
790 }
791 }
792 encodings.sort();
793 if (usAscii) {
794 encodings.prepend(KCharsets::charsets()->descriptionForEncoding(QStringLiteral("us-ascii")));
795 }
796 return encodings;
797}
798
799QString NodeHelper::fromAsString(KMime::Content *node) const
800{
801 if (auto topLevel = dynamic_cast<KMime::Message *>(node->topLevel())) {
802 return topLevel->from()->asUnicodeString();
803 } else {
804 auto realNode = std::find_if(mExtraContents.cbegin(), mExtraContents.cend(),
805 [node](const QList<KMime::Content *> &nodes) {
806 return nodes.contains(node);
807 });
808 if (realNode != mExtraContents.cend()) {
809 return fromAsString(realNode.key());
810 }
811 }
812
813 return QString();
814}
815
816void NodeHelper::attachExtraContent(KMime::Content *topLevelNode, KMime::Content *content)
817{
818 qCDebug(MIMETREEPARSER_LOG) << "mExtraContents added for" << topLevelNode << " extra content: " << content;
819 mExtraContents[topLevelNode].append(content);
820}
821
822QList< KMime::Content * > NodeHelper::extraContents(KMime::Content *topLevelnode) const
823{
824 return mExtraContents.value(topLevelnode);
825}
826
827void NodeHelper::mergeExtraNodes(KMime::Content *node)
828{
829 if (!node) {
830 return;
831 }
832
833 const QList<KMime::Content * > extraNodes = extraContents(node);
834 for (KMime::Content *extra : extraNodes) {
835 if (node->bodyIsMessage()) {
836 qCWarning(MIMETREEPARSER_LOG) << "Asked to attach extra content to a kmime::message, this does not make sense. Attaching to:" << node <<
837 node->encodedContent() << "\n====== with =======\n" << extra << extra->encodedContent();
838 continue;
839 }
840 KMime::Content *c = new KMime::Content(node);
841 c->setContent(extra->encodedContent());
842 c->parse();
843 node->addContent(c);
844 }
845
846 Q_FOREACH (KMime::Content *child, node->contents()) {
847 mergeExtraNodes(child);
848 }
849}
850
851void NodeHelper::cleanFromExtraNodes(KMime::Content *node)
852{
853 if (!node) {
854 return;
855 }
856 const QList<KMime::Content * > extraNodes = extraContents(node);
857 for (KMime::Content *extra : extraNodes) {
858 QByteArray s = extra->encodedContent();
859 const auto children = node->contents();
860 for (KMime::Content *c : children) {
861 if (c->encodedContent() == s) {
862 node->removeContent(c);
863 }
864 }
865 }
866 Q_FOREACH (KMime::Content *child, node->contents()) {
867 cleanFromExtraNodes(child);
868 }
869}
870
871KMime::Message *NodeHelper::messageWithExtraContent(KMime::Content *topLevelNode)
872{
873 /*The merge is done in several steps:
874 1) merge the extra nodes into topLevelNode
875 2) copy the modified (merged) node tree into a new node tree
876 3) restore the original node tree in topLevelNode by removing the extra nodes from it
877
878 The reason is that extra nodes are assigned by pointer value to the nodes in the original tree.
879 */
880 if (!topLevelNode) {
881 return nullptr;
882 }
883
884 mergeExtraNodes(topLevelNode);
885
886 KMime::Message *m = new KMime::Message;
887 m->setContent(topLevelNode->encodedContent());
888 m->parse();
889
890 cleanFromExtraNodes(topLevelNode);
891// qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITH EXTRA: " << m->encodedContent();
892// qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITHOUT EXTRA: " << topLevelNode->encodedContent();
893
894 return m;
895}
896
897KMime::Content *NodeHelper::decryptedNodeForContent(KMime::Content *content) const
898{
899 const QList<KMime::Content *> xc = extraContents(content);
900 if (!xc.empty()) {
901 if (xc.size() == 1) {
902 return xc.front();
903 } else {
904 qCWarning(MIMETREEPARSER_LOG) << "WTF, encrypted node has multiple extra contents?";
905 }
906 }
907 return nullptr;
908}
909
910bool NodeHelper::unencryptedMessage_helper(KMime::Content *node, QByteArray &resultingData, bool addHeaders,
911 int recursionLevel)
912{
913 bool returnValue = false;
914 if (node) {
915 KMime::Content *curNode = node;
916 KMime::Content *decryptedNode = nullptr;
917 const QByteArray type = node->contentType(false) ? QByteArray(node->contentType()->mediaType()).toLower() : "text";
918 const QByteArray subType = node->contentType(false) ? node->contentType()->subType().toLower() : "plain";
919 const bool isMultipart = node->contentType(false) && node->contentType()->isMultipart();
920 bool isSignature = false;
921
922 qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") Looking at" << type << "/" << subType;
923
924 if (isMultipart) {
925 if (subType == "signed") {
926 isSignature = true;
927 } else if (subType == "encrypted") {
928 decryptedNode = decryptedNodeForContent(curNode);
929 }
930 } else if (type == "application") {
931 if (subType == "octet-stream") {
932 decryptedNode = decryptedNodeForContent(curNode);
933 } else if (subType == "pkcs7-signature") {
934 isSignature = true;
935 } else if (subType == "pkcs7-mime") {
936 // note: subtype pkcs7-mime can also be signed
937 // and we do NOT want to remove the signature!
938 if (encryptionState(curNode) != KMMsgNotEncrypted) {
939 decryptedNode = decryptedNodeForContent(curNode);
940 }
941 }
942 }
943
944 if (decryptedNode) {
945 qCDebug(MIMETREEPARSER_LOG) << "Current node has an associated decrypted node, adding a modified header "
946 "and then processing the children.";
947
948 Q_ASSERT(addHeaders);
949 KMime::Content headers;
950 headers.setHead(curNode->head());
951 headers.parse();
952 if (decryptedNode->contentType(false)) {
953 headers.contentType()->from7BitString(decryptedNode->contentType()->as7BitString(false));
954 } else {
955 headers.removeHeader<KMime::Headers::ContentType>();
956 }
957 if (decryptedNode->contentTransferEncoding(false)) {
958 headers.contentTransferEncoding()->from7BitString(decryptedNode->contentTransferEncoding()->as7BitString(false));
959 } else {
960 headers.removeHeader<KMime::Headers::ContentTransferEncoding>();
961 }
962 if (decryptedNode->contentDisposition(false)) {
963 headers.contentDisposition()->from7BitString(decryptedNode->contentDisposition()->as7BitString(false));
964 } else {
965 headers.removeHeader<KMime::Headers::ContentDisposition>();
966 }
967 if (decryptedNode->contentDescription(false)) {
968 headers.contentDescription()->from7BitString(decryptedNode->contentDescription()->as7BitString(false));
969 } else {
970 headers.removeHeader<KMime::Headers::ContentDescription>();
971 }
972 headers.assemble();
973
974 resultingData += headers.head() + '\n';
975 unencryptedMessage_helper(decryptedNode, resultingData, false, recursionLevel + 1);
976
977 returnValue = true;
978 }
979
980 else if (isSignature) {
981 qCDebug(MIMETREEPARSER_LOG) << "Current node is a signature, adding it as-is.";
982 // We can't change the nodes under the signature, as that would invalidate it. Add the signature
983 // and its child as-is
984 if (addHeaders) {
985 resultingData += curNode->head() + '\n';
986 }
987 resultingData += curNode->encodedBody();
988 returnValue = false;
989 }
990
991 else if (isMultipart) {
992 qCDebug(MIMETREEPARSER_LOG) << "Current node is a multipart node, adding its header and then processing all children.";
993 // Normal multipart node, add the header and all of its children
994 bool somethingChanged = false;
995 if (addHeaders) {
996 resultingData += curNode->head() + '\n';
997 }
998 const QByteArray boundary = curNode->contentType()->boundary();
999 foreach (KMime::Content *child, curNode->contents()) {
1000 resultingData += "\n--" + boundary + '\n';
1001 const bool changed = unencryptedMessage_helper(child, resultingData, true, recursionLevel + 1);
1002 if (changed) {
1003 somethingChanged = true;
1004 }
1005 }
1006 resultingData += "\n--" + boundary + "--\n\n";
1007 returnValue = somethingChanged;
1008 }
1009
1010 else if (curNode->bodyIsMessage()) {
1011 qCDebug(MIMETREEPARSER_LOG) << "Current node is a message, adding the header and then processing the child.";
1012 if (addHeaders) {
1013 resultingData += curNode->head() + '\n';
1014 }
1015
1016 returnValue = unencryptedMessage_helper(curNode->bodyAsMessage().data(), resultingData, true, recursionLevel + 1);
1017 }
1018
1019 else {
1020 qCDebug(MIMETREEPARSER_LOG) << "Current node is an ordinary leaf node, adding it as-is.";
1021 if (addHeaders) {
1022 resultingData += curNode->head() + '\n';
1023 }
1024 resultingData += curNode->body();
1025 returnValue = false;
1026 }
1027 }
1028
1029 qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") done.";
1030 return returnValue;
1031}
1032
1033KMime::Message::Ptr NodeHelper::unencryptedMessage(const KMime::Message::Ptr &originalMessage)
1034{
1035 QByteArray resultingData;
1036 const bool messageChanged = unencryptedMessage_helper(originalMessage.data(), resultingData, true);
1037 if (messageChanged) {
1038#if 0
1039 qCDebug(MIMETREEPARSER_LOG) << "Resulting data is:" << resultingData;
1040 QFile bla("stripped.mbox");
1041 bla.open(QIODevice::WriteOnly);
1042 bla.write(resultingData);
1043 bla.close();
1044#endif
1045 KMime::Message::Ptr newMessage(new KMime::Message);
1046 newMessage->setContent(resultingData);
1047 newMessage->parse();
1048 return newMessage;
1049 } else {
1050 return KMime::Message::Ptr();
1051 }
1052}
1053
1054QVector<KMime::Content *> NodeHelper::attachmentsOfExtraContents() const
1055{
1056 QVector<KMime::Content *> result;
1057 for (auto it = mExtraContents.begin(); it != mExtraContents.end(); ++it) {
1058 foreach (auto content, it.value()) {
1059 if (KMime::isAttachment(content)) {
1060 result.push_back(content);
1061 } else {
1062 result += content->attachments();
1063 }
1064 }
1065 }
1066 return result;
1067}
1068
1069}