Messagelib

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

KDE's Doxygen guidelines are available online.