summaryrefslogtreecommitdiffstats
path: root/framework/src/domain/mime/mimetreeparser/nodehelper.cpp
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2017-05-29 16:17:04 +0200
committerChristian Mollekopf <chrigi_1@fastmail.fm>2017-06-04 12:57:04 +0200
commite452707fdfbd61be1e5633b516b653b7337e7865 (patch)
tree1e1d4b48ebf8d381f292436f2ba04b8763edc5de /framework/src/domain/mime/mimetreeparser/nodehelper.cpp
parent5a1033bdace740799a6e03389bee30e5a4de5d44 (diff)
downloadkube-e452707fdfbd61be1e5633b516b653b7337e7865.tar.gz
kube-e452707fdfbd61be1e5633b516b653b7337e7865.zip
Reduced the messagetreeparser to aproximately what we actually require
While in a much more managable state it's still not pretty. However, further refactoring can now gradually happen as we need to do further work on it. Things that should happen eventually: * Simplify the logic that creates the messageparts (we don't need the whole formatter plugin complexity) * Get rid of the nodehelper (let the parts hold the necessary data) * Get rid of partmetadata (let the part handleit)
Diffstat (limited to 'framework/src/domain/mime/mimetreeparser/nodehelper.cpp')
-rw-r--r--framework/src/domain/mime/mimetreeparser/nodehelper.cpp544
1 files changed, 544 insertions, 0 deletions
diff --git a/framework/src/domain/mime/mimetreeparser/nodehelper.cpp b/framework/src/domain/mime/mimetreeparser/nodehelper.cpp
new file mode 100644
index 00000000..3005ea0f
--- /dev/null
+++ b/framework/src/domain/mime/mimetreeparser/nodehelper.cpp
@@ -0,0 +1,544 @@
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
143void NodeHelper::clear()
144{
145 mProcessedNodes.clear();
146 mOverrideCodecs.clear();
147 QMap<KMime::Content *, QList<KMime::Content *> >::ConstIterator end(mExtraContents.constEnd());
148
149 for (QMap<KMime::Content *, QList<KMime::Content *> >::ConstIterator it = mExtraContents.constBegin(); it != end; ++it) {
150 Q_FOREACH (KMime::Content *c, it.value()) {
151 KMime::Content *p = c->parent();
152 if (p) {
153 p->removeContent(c);
154 }
155 }
156 qDeleteAll(it.value());
157 qCDebug(MIMETREEPARSER_LOG) << "mExtraContents deleted for" << it.key();
158 }
159 mExtraContents.clear();
160}
161
162
163PartMetaData NodeHelper::partMetaData(KMime::Content *node)
164{
165 return mPartMetaDatas.value(node, PartMetaData());
166}
167
168void NodeHelper::setPartMetaData(KMime::Content *node, const PartMetaData &metaData)
169{
170 mPartMetaDatas.insert(node, metaData);
171}
172
173void NodeHelper::forceCleanTempFiles()
174{
175 mAttachmentFilesDir->forceCleanTempFiles();
176 delete mAttachmentFilesDir;
177 mAttachmentFilesDir = nullptr;
178}
179
180void NodeHelper::removeTempFiles()
181{
182 //Don't delete it it will delete in class
183 mAttachmentFilesDir->removeTempFiles();
184 mAttachmentFilesDir = new AttachmentTemporaryFilesDirs();
185}
186
187void NodeHelper::addTempFile(const QString &file)
188{
189 mAttachmentFilesDir->addTempFile(file);
190}
191
192bool NodeHelper::isInEncapsulatedMessage(KMime::Content *node)
193{
194 const KMime::Content *const topLevel = node->topLevel();
195 const KMime::Content *cur = node;
196 while (cur && cur != topLevel) {
197 const bool parentIsMessage = cur->parent() && cur->parent()->contentType(false) &&
198 cur->parent()->contentType()->mimeType().toLower() == "message/rfc822";
199 if (parentIsMessage && cur->parent() != topLevel) {
200 return true;
201 }
202 cur = cur->parent();
203 }
204 return false;
205}
206
207QByteArray NodeHelper::charset(KMime::Content *node)
208{
209 if (node->contentType(false)) {
210 return node->contentType(false)->charset();
211 } else {
212 return node->defaultCharset();
213 }
214}
215
216void NodeHelper::magicSetType(KMime::Content *node, bool aAutoDecode)
217{
218 const QByteArray body = (aAutoDecode) ? node->decodedContent() : node->body();
219 QMimeDatabase db;
220 QMimeType mime = db.mimeTypeForData(body);
221
222 QString mimetype = mime.name();
223 node->contentType()->setMimeType(mimetype.toLatin1());
224}
225
226void NodeHelper::setOverrideCodec(KMime::Content *node, const QTextCodec *codec)
227{
228 if (!node) {
229 return;
230 }
231
232 mOverrideCodecs[node] = codec;
233}
234
235const QTextCodec *NodeHelper::codec(KMime::Content *node)
236{
237 if (! node) {
238 return mLocalCodec;
239 }
240
241 const QTextCodec *c = mOverrideCodecs.value(node, nullptr);
242 if (!c) {
243 // no override-codec set for this message, try the CT charset parameter:
244 QByteArray charset = node->contentType()->charset();
245
246 // utf-8 is a superset of us-ascii, so we don't loose anything, if we it insead
247 // utf-8 is nowadays that widely, that it is a good guess to use it to fix issus with broken clients.
248 if (charset.toLower() == "us-ascii") {
249 charset = "utf-8";
250 }
251 c = codecForName(charset);
252 }
253 if (!c) {
254 // no charset means us-ascii (RFC 2045), so using local encoding should
255 // be okay
256 c = mLocalCodec;
257 }
258 return c;
259}
260
261const QTextCodec *NodeHelper::codecForName(const QByteArray &_str)
262{
263 if (_str.isEmpty()) {
264 return nullptr;
265 }
266 QByteArray codec = _str.toLower();
267 return KCharsets::charsets()->codecForName(QLatin1String(codec));
268}
269
270QString NodeHelper::fileName(const KMime::Content *node)
271{
272 QString name = const_cast<KMime::Content *>(node)->contentDisposition()->filename();
273 if (name.isEmpty()) {
274 name = const_cast<KMime::Content *>(node)->contentType()->name();
275 }
276
277 name = name.trimmed();
278 return name;
279}
280
281/*!
282 Creates a persistent index string that bridges the gap between the
283 permanent nodes and the temporary ones.
284
285 Used internally for robust indexing.
286*/
287QString NodeHelper::persistentIndex(const KMime::Content *node) const
288{
289 if (!node) {
290 return QString();
291 }
292
293 QString indexStr = node->index().toString();
294 if (indexStr.isEmpty()) {
295 QMapIterator<KMime::Message::Content *, QList<KMime::Content *> > it(mExtraContents);
296 while (it.hasNext()) {
297 it.next();
298 const auto &extraNodes = it.value();
299 for (int i = 0; i < extraNodes.size(); i++) {
300 if (extraNodes[i] == node) {
301 indexStr = QString::fromLatin1("e%1").arg(i);
302 const QString parentIndex = persistentIndex(it.key());
303 if (!parentIndex.isEmpty()) {
304 indexStr = QString::fromLatin1("%1:%2").arg(parentIndex, indexStr);
305 }
306 qWarning() << "Persistentindex: " << indexStr;
307 return indexStr;
308 }
309 }
310 }
311 } else {
312 const KMime::Content *const topLevel = node->topLevel();
313 //if the node is an extra node, prepend the index of the extra node to the url
314 QMapIterator<KMime::Message::Content *, QList<KMime::Content *> > it(mExtraContents);
315 while (it.hasNext()) {
316 it.next();
317 const QList<KMime::Content *> &extraNodes = extraContents(it.key());
318 for (int i = 0; i < extraNodes.size(); ++i) {
319 KMime::Content *const extraNode = extraNodes[i];
320 if (topLevel == extraNode) {
321 indexStr.prepend(QStringLiteral("e%1:").arg(i));
322 const QString parentIndex = persistentIndex(it.key());
323 if (!parentIndex.isEmpty()) {
324 indexStr = QStringLiteral("%1:%2").arg(parentIndex, indexStr);
325 }
326 qWarning() << "Persistentindex: " << indexStr;
327 return indexStr;
328 }
329 }
330 }
331 }
332
333 qWarning() << "Persistentindex: " << indexStr;
334 return indexStr;
335}
336
337KMime::Content *NodeHelper::contentFromIndex(KMime::Content *node, const QString &persistentIndex) const
338{
339 KMime::Content *c = node->topLevel();
340 if (c) {
341 const QStringList pathParts = persistentIndex.split(QLatin1Char(':'), QString::SkipEmptyParts);
342 const int pathPartsSize(pathParts.size());
343 for (int i = 0; i < pathPartsSize; ++i) {
344 const QString &path = pathParts[i];
345 if (path.startsWith(QLatin1Char('e'))) {
346 const QList<KMime::Content *> &extraParts = mExtraContents.value(c);
347 const int idx = path.midRef(1, -1).toInt();
348 c = (idx < extraParts.size()) ? extraParts[idx] : nullptr;
349 } else {
350 c = c->content(KMime::ContentIndex(path));
351 }
352 if (!c) {
353 break;
354 }
355 }
356 }
357 return c;
358}
359
360QString NodeHelper::asHREF(const KMime::Content *node, const QString &place) const
361{
362 return QStringLiteral("attachment:%1?place=%2").arg(persistentIndex(node), place);
363}
364
365KMime::Content *NodeHelper::fromHREF(const KMime::Message::Ptr &mMessage, const QUrl &url) const
366{
367 if (url.isEmpty()) {
368 return mMessage.data();
369 }
370
371 if (!url.isLocalFile()) {
372 return contentFromIndex(mMessage.data(), url.adjusted(QUrl::StripTrailingSlash).path());
373 } else {
374 const QString path = url.toLocalFile();
375 // extract from /<path>/qttestn28554.index.2.3:0:2/unnamed -> "2.3:0:2"
376 // start of the index is something that is not a number followed by a dot: \D.
377 // index is only made of numbers,"." and ":": ([0-9.:]+)
378 // index is the last part of the folder name: /
379 const QRegExp rIndex(QStringLiteral("\\D\\.([e0-9.:]+)/"));
380
381 //search the occurence at most at the end
382 if (rIndex.lastIndexIn(path) != -1) {
383 return contentFromIndex(mMessage.data(), rIndex.cap(1));
384 }
385 return mMessage.data();
386 }
387}
388
389QString NodeHelper::fixEncoding(const QString &encoding)
390{
391 QString returnEncoding = encoding;
392 // According to http://www.iana.org/assignments/character-sets, uppercase is
393 // preferred in MIME headers
394 const QString returnEncodingToUpper = returnEncoding.toUpper();
395 if (returnEncodingToUpper.contains(QStringLiteral("ISO "))) {
396 returnEncoding = returnEncodingToUpper;
397 returnEncoding.replace(QLatin1String("ISO "), QStringLiteral("ISO-"));
398 }
399 return returnEncoding;
400}
401
402//-----------------------------------------------------------------------------
403QString NodeHelper::encodingForName(const QString &descriptiveName)
404{
405 QString encoding = KCharsets::charsets()->encodingForName(descriptiveName);
406 return NodeHelper::fixEncoding(encoding);
407}
408
409QStringList NodeHelper::supportedEncodings(bool usAscii)
410{
411 QStringList encodingNames = KCharsets::charsets()->availableEncodingNames();
412 QStringList encodings;
413 QMap<QString, bool> mimeNames;
414 QStringList::ConstIterator constEnd(encodingNames.constEnd());
415 for (QStringList::ConstIterator it = encodingNames.constBegin();
416 it != constEnd; ++it) {
417 QTextCodec *codec = KCharsets::charsets()->codecForName(*it);
418 QString mimeName = (codec) ? QString::fromLatin1(codec->name()).toLower() : (*it);
419 if (!mimeNames.contains(mimeName)) {
420 encodings.append(KCharsets::charsets()->descriptionForEncoding(*it));
421 mimeNames.insert(mimeName, true);
422 }
423 }
424 encodings.sort();
425 if (usAscii) {
426 encodings.prepend(KCharsets::charsets()->descriptionForEncoding(QStringLiteral("us-ascii")));
427 }
428 return encodings;
429}
430
431QString NodeHelper::fromAsString(KMime::Content *node) const
432{
433 if (auto topLevel = dynamic_cast<KMime::Message *>(node->topLevel())) {
434 return topLevel->from()->asUnicodeString();
435 } else {
436 auto realNode = std::find_if(mExtraContents.cbegin(), mExtraContents.cend(),
437 [node](const QList<KMime::Content *> &nodes) {
438 return nodes.contains(node);
439 });
440 if (realNode != mExtraContents.cend()) {
441 return fromAsString(realNode.key());
442 }
443 }
444
445 return QString();
446}
447
448void NodeHelper::attachExtraContent(KMime::Content *topLevelNode, KMime::Content *content)
449{
450 qCDebug(MIMETREEPARSER_LOG) << "mExtraContents added for" << topLevelNode << " extra content: " << content;
451 mExtraContents[topLevelNode].append(content);
452}
453
454QList< KMime::Content * > NodeHelper::extraContents(KMime::Content *topLevelnode) const
455{
456 return mExtraContents.value(topLevelnode);
457}
458
459void NodeHelper::mergeExtraNodes(KMime::Content *node)
460{
461 if (!node) {
462 return;
463 }
464
465 const QList<KMime::Content * > extraNodes = extraContents(node);
466 for (KMime::Content *extra : extraNodes) {
467 if (node->bodyIsMessage()) {
468 qCWarning(MIMETREEPARSER_LOG) << "Asked to attach extra content to a kmime::message, this does not make sense. Attaching to:" << node <<
469 node->encodedContent() << "\n====== with =======\n" << extra << extra->encodedContent();
470 continue;
471 }
472 KMime::Content *c = new KMime::Content(node);
473 c->setContent(extra->encodedContent());
474 c->parse();
475 node->addContent(c);
476 }
477
478 Q_FOREACH (KMime::Content *child, node->contents()) {
479 mergeExtraNodes(child);
480 }
481}
482
483void NodeHelper::cleanFromExtraNodes(KMime::Content *node)
484{
485 if (!node) {
486 return;
487 }
488 const QList<KMime::Content * > extraNodes = extraContents(node);
489 for (KMime::Content *extra : extraNodes) {
490 QByteArray s = extra->encodedContent();
491 const auto children = node->contents();
492 for (KMime::Content *c : children) {
493 if (c->encodedContent() == s) {
494 node->removeContent(c);
495 }
496 }
497 }
498 Q_FOREACH (KMime::Content *child, node->contents()) {
499 cleanFromExtraNodes(child);
500 }
501}
502
503KMime::Message *NodeHelper::messageWithExtraContent(KMime::Content *topLevelNode)
504{
505 /*The merge is done in several steps:
506 1) merge the extra nodes into topLevelNode
507 2) copy the modified (merged) node tree into a new node tree
508 3) restore the original node tree in topLevelNode by removing the extra nodes from it
509
510 The reason is that extra nodes are assigned by pointer value to the nodes in the original tree.
511 */
512 if (!topLevelNode) {
513 return nullptr;
514 }
515
516 mergeExtraNodes(topLevelNode);
517
518 KMime::Message *m = new KMime::Message;
519 m->setContent(topLevelNode->encodedContent());
520 m->parse();
521
522 cleanFromExtraNodes(topLevelNode);
523// qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITH EXTRA: " << m->encodedContent();
524// qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITHOUT EXTRA: " << topLevelNode->encodedContent();
525
526 return m;
527}
528
529QVector<KMime::Content *> NodeHelper::attachmentsOfExtraContents() const
530{
531 QVector<KMime::Content *> result;
532 for (auto it = mExtraContents.begin(); it != mExtraContents.end(); ++it) {
533 foreach (auto content, it.value()) {
534 if (KMime::isAttachment(content)) {
535 result.push_back(content);
536 } else {
537 result += content->attachments();
538 }
539 }
540 }
541 return result;
542}
543
544}