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

KDE's Doxygen guidelines are available online.