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