diff options
Diffstat (limited to 'framework/src/domain/mime/mimetreeparser/otp/nodehelper.cpp')
-rw-r--r-- | framework/src/domain/mime/mimetreeparser/otp/nodehelper.cpp | 1011 |
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 | |||
46 | namespace MimeTreeParser | ||
47 | { | ||
48 | |||
49 | NodeHelper::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 | |||
79 | NodeHelper::~NodeHelper() | ||
80 | { | ||
81 | if (mAttachmentFilesDir) { | ||
82 | mAttachmentFilesDir->forceCleanTempFiles(); | ||
83 | delete mAttachmentFilesDir; | ||
84 | mAttachmentFilesDir = nullptr; | ||
85 | } | ||
86 | clear(); | ||
87 | } | ||
88 | |||
89 | void 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 | |||
105 | void 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 | |||
135 | bool NodeHelper::nodeProcessed(KMime::Content *node) const | ||
136 | { | ||
137 | if (!node) { | ||
138 | return true; | ||
139 | } | ||
140 | return mProcessedNodes.contains(node); | ||
141 | } | ||
142 | |||
143 | static 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 | |||
155 | void 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 | |||
181 | void NodeHelper::setEncryptionState(const KMime::Content *node, const KMMsgEncryptionState state) | ||
182 | { | ||
183 | mEncryptionState[node] = state; | ||
184 | } | ||
185 | |||
186 | KMMsgEncryptionState NodeHelper::encryptionState(const KMime::Content *node) const | ||
187 | { | ||
188 | return mEncryptionState.value(node, KMMsgNotEncrypted); | ||
189 | } | ||
190 | |||
191 | void NodeHelper::setSignatureState(const KMime::Content *node, const KMMsgSignatureState state) | ||
192 | { | ||
193 | mSignatureState[node] = state; | ||
194 | } | ||
195 | |||
196 | KMMsgSignatureState NodeHelper::signatureState(const KMime::Content *node) const | ||
197 | { | ||
198 | return mSignatureState.value(node, KMMsgNotSigned); | ||
199 | } | ||
200 | |||
201 | PartMetaData NodeHelper::partMetaData(KMime::Content *node) | ||
202 | { | ||
203 | return mPartMetaDatas.value(node, PartMetaData()); | ||
204 | } | ||
205 | |||
206 | void NodeHelper::setPartMetaData(KMime::Content *node, const PartMetaData &metaData) | ||
207 | { | ||
208 | mPartMetaDatas.insert(node, metaData); | ||
209 | } | ||
210 | |||
211 | QString 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 | |||
258 | QUrl 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 | |||
281 | QString NodeHelper::createTempDir(const QString ¶m) | ||
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 | |||
303 | void NodeHelper::forceCleanTempFiles() | ||
304 | { | ||
305 | mAttachmentFilesDir->forceCleanTempFiles(); | ||
306 | delete mAttachmentFilesDir; | ||
307 | mAttachmentFilesDir = nullptr; | ||
308 | } | ||
309 | |||
310 | void NodeHelper::removeTempFiles() | ||
311 | { | ||
312 | //Don't delete it it will delete in class | ||
313 | mAttachmentFilesDir->removeTempFiles(); | ||
314 | mAttachmentFilesDir = new AttachmentTemporaryFilesDirs(); | ||
315 | } | ||
316 | |||
317 | void NodeHelper::addTempFile(const QString &file) | ||
318 | { | ||
319 | mAttachmentFilesDir->addTempFile(file); | ||
320 | } | ||
321 | |||
322 | bool 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 | |||
337 | QByteArray 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 | |||
346 | KMMsgEncryptionState 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 | |||
404 | KMMsgSignatureState 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 | |||
462 | void 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 | |||
472 | void NodeHelper::setOverrideCodec(KMime::Content *node, const QTextCodec *codec) | ||
473 | { | ||
474 | if (!node) { | ||
475 | return; | ||
476 | } | ||
477 | |||
478 | mOverrideCodecs[node] = codec; | ||
479 | } | ||
480 | |||
481 | const 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 | |||
507 | const 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 | |||
516 | QString 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 | ||
528 | Interface::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 | ||
542 | void 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 | |||
564 | bool NodeHelper::isNodeDisplayedEmbedded(KMime::Content *node) const | ||
565 | { | ||
566 | qCDebug(MIMETREEPARSER_LOG) << "IS NODE: " << mDisplayEmbeddedNodes.contains(node); | ||
567 | return mDisplayEmbeddedNodes.contains(node); | ||
568 | } | ||
569 | |||
570 | void 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 | |||
580 | bool NodeHelper::isNodeDisplayedHidden(KMime::Content *node) const | ||
581 | { | ||
582 | return mDisplayHiddenNodes.contains(node); | ||
583 | } | ||
584 | |||
585 | void 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 | */ | ||
600 | QString 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 | |||
647 | KMime::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 | |||
670 | QString NodeHelper::asHREF(const KMime::Content *node, const QString &place) const | ||
671 | { | ||
672 | return QStringLiteral("attachment:%1?place=%2").arg(persistentIndex(node), place); | ||
673 | } | ||
674 | |||
675 | KMime::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 | |||
699 | QString 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 | //----------------------------------------------------------------------------- | ||
713 | QString NodeHelper::encodingForName(const QString &descriptiveName) | ||
714 | { | ||
715 | QString encoding = KCharsets::charsets()->encodingForName(descriptiveName); | ||
716 | return NodeHelper::fixEncoding(encoding); | ||
717 | } | ||
718 | |||
719 | QStringList 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 | |||
741 | QString 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 | |||
758 | void 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 | |||
764 | QList< KMime::Content * > NodeHelper::extraContents(KMime::Content *topLevelnode) const | ||
765 | { | ||
766 | return mExtraContents.value(topLevelnode); | ||
767 | } | ||
768 | |||
769 | void 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 | |||
793 | void 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 | |||
813 | KMime::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 | |||
839 | KMime::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 | |||
852 | bool 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 | |||
975 | KMime::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 | |||
996 | QVector<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 | } | ||