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 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
285  const QStringRef storedIndex(&path, left, right - left);
286  if (left != -1 && storedIndex == index) {
287  return QUrl::fromLocalFile(path);
288  }
289 #else
290  const QStringView strView(path);
291  const QStringView storedIndex = strView.sliced(left, right - left);
292  if (left != -1 && storedIndex == index) {
293  return QUrl::fromLocalFile(path);
294  }
295 
296 #endif
297  }
298  return {};
299 }
300 
301 QString NodeHelper::createTempDir(const QString &param)
302 {
303  auto tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/messageviewer_XXXXXX") + QLatin1String(".index.") + param);
304  tempFile->open();
305  const QString fname = tempFile->fileName();
306  delete tempFile;
307 
308  QFile fFile(fname);
309  if (!(fFile.permissions() & QFileDevice::WriteUser)) {
310  // Not there or not writable
312  mAttachmentFilesDir->addTempDir(fname);
313  return {}; // failed create
314  }
315  }
316 
317  Q_ASSERT(!fname.isNull());
318 
319  mAttachmentFilesDir->addTempDir(fname);
320  return fname;
321 }
322 
323 void NodeHelper::forceCleanTempFiles()
324 {
325  mAttachmentFilesDir->forceCleanTempFiles();
326  delete mAttachmentFilesDir;
327  mAttachmentFilesDir = nullptr;
328 }
329 
330 void NodeHelper::removeTempFiles()
331 {
332  // Don't delete as it will be deleted in class
333  if (mAttachmentFilesDir) {
334  mAttachmentFilesDir->removeTempFiles();
335  }
336  mAttachmentFilesDir = new AttachmentTemporaryFilesDirs();
337  mListAttachmentTemporaryDirs.append(mAttachmentFilesDir);
338 }
339 
340 void NodeHelper::addTempFile(const QString &file)
341 {
342  mAttachmentFilesDir->addTempFile(file);
343 }
344 
345 bool NodeHelper::isInEncapsulatedMessage(KMime::Content *node)
346 {
347  const KMime::Content *const topLevel = node->topLevel();
348  const KMime::Content *cur = node;
349  while (cur && cur != topLevel) {
350  const bool parentIsMessage =
351  cur->parent() && cur->parent()->contentType(false) && cur->parent()->contentType(false)->mimeType().toLower() == "message/rfc822";
352  if (parentIsMessage && cur->parent() != topLevel) {
353  return true;
354  }
355  cur = cur->parent();
356  }
357  return false;
358 }
359 
360 QByteArray NodeHelper::charset(KMime::Content *node)
361 {
362  if (node->contentType(false)) {
363  return node->contentType(false)->charset();
364  } else {
365  return node->defaultCharset();
366  }
367 }
368 
369 KMMsgEncryptionState NodeHelper::overallEncryptionState(KMime::Content *node) const
370 {
371  KMMsgEncryptionState myState = KMMsgEncryptionStateUnknown;
372  if (!node) {
373  return myState;
374  }
375 
376  KMime::Content *parent = node->parent();
377  auto contents = parent ? parent->contents() : KMime::Content::List();
378  if (contents.isEmpty()) {
379  contents.append(node);
380  }
381  int i = contents.indexOf(const_cast<KMime::Content *>(node));
382  if (i < 0) {
383  return myState;
384  }
385  for (; i < contents.size(); ++i) {
386  auto next = contents.at(i);
387  KMMsgEncryptionState otherState = encryptionState(next);
388 
389  // NOTE: children are tested ONLY when parent is not encrypted
390  if (otherState == KMMsgNotEncrypted && !next->contents().isEmpty()) {
391  otherState = overallEncryptionState(next->contents().at(0));
392  }
393 
394  if (otherState == KMMsgNotEncrypted && !extraContents(next).isEmpty()) {
395  otherState = overallEncryptionState(extraContents(next).at(0));
396  }
397 
398  if (next == node) {
399  myState = otherState;
400  }
401 
402  switch (otherState) {
403  case KMMsgEncryptionStateUnknown:
404  break;
405  case KMMsgNotEncrypted:
406  if (myState == KMMsgFullyEncrypted) {
407  myState = KMMsgPartiallyEncrypted;
408  } else if (myState != KMMsgPartiallyEncrypted) {
409  myState = KMMsgNotEncrypted;
410  }
411  break;
412  case KMMsgPartiallyEncrypted:
413  myState = KMMsgPartiallyEncrypted;
414  break;
415  case KMMsgFullyEncrypted:
416  if (myState != KMMsgFullyEncrypted) {
417  myState = KMMsgPartiallyEncrypted;
418  }
419  break;
420  case KMMsgEncryptionProblematic:
421  break;
422  }
423  }
424 
425  qCDebug(MIMETREEPARSER_LOG) << "\n\n KMMsgEncryptionState:" << myState;
426 
427  return myState;
428 }
429 
430 KMMsgSignatureState NodeHelper::overallSignatureState(KMime::Content *node) const
431 {
432  KMMsgSignatureState myState = KMMsgSignatureStateUnknown;
433  if (!node) {
434  return myState;
435  }
436 
437  KMime::Content *parent = node->parent();
438  auto contents = parent ? parent->contents() : KMime::Content::List();
439  if (contents.isEmpty()) {
440  contents.append(node);
441  }
442  int i = contents.indexOf(const_cast<KMime::Content *>(node));
443  if (i < 0) { // Be safe
444  return myState;
445  }
446  for (; i < contents.size(); ++i) {
447  auto next = contents.at(i);
448  KMMsgSignatureState otherState = signatureState(next);
449 
450  // NOTE: children are tested ONLY when parent is not encrypted
451  if (otherState == KMMsgNotSigned && !next->contents().isEmpty()) {
452  otherState = overallSignatureState(next->contents().at(0));
453  }
454 
455  if (otherState == KMMsgNotSigned && !extraContents(next).isEmpty()) {
456  otherState = overallSignatureState(extraContents(next).at(0));
457  }
458 
459  if (next == node) {
460  myState = otherState;
461  }
462 
463  switch (otherState) {
464  case KMMsgSignatureStateUnknown:
465  break;
466  case KMMsgNotSigned:
467  if (myState == KMMsgFullySigned) {
468  myState = KMMsgPartiallySigned;
469  } else if (myState != KMMsgPartiallySigned) {
470  myState = KMMsgNotSigned;
471  }
472  break;
473  case KMMsgPartiallySigned:
474  myState = KMMsgPartiallySigned;
475  break;
476  case KMMsgFullySigned:
477  if (myState != KMMsgFullySigned) {
478  myState = KMMsgPartiallySigned;
479  }
480  break;
481  case KMMsgSignatureProblematic:
482  break;
483  }
484  }
485 
486  qCDebug(MIMETREEPARSER_LOG) << "\n\n KMMsgSignatureState:" << myState;
487 
488  return myState;
489 }
490 
491 void NodeHelper::magicSetType(KMime::Content *node, bool aAutoDecode)
492 {
493  const QByteArray body = aAutoDecode ? node->decodedContent() : node->body();
494  QMimeDatabase db;
495  const QMimeType mime = db.mimeTypeForData(body);
496 
497  const QString mimetype = mime.name();
498  node->contentType()->setMimeType(mimetype.toLatin1());
499 }
500 
501 bool NodeHelper::hasMailHeader(const char *header, const KMime::Content *message) const
502 {
503  if (mHeaderOverwrite.contains(message)) {
504  const auto parts = mHeaderOverwrite.value(message);
505  for (const auto &messagePart : parts) {
506  if (messagePart->hasHeader(header)) {
507  return true;
508  }
509  }
510  }
511  return message->hasHeader(header);
512 }
513 
514 QVector<MessagePart::Ptr> NodeHelper::messagePartsOfMailHeader(const char *header, const KMime::Content *message) const
515 {
517  if (mHeaderOverwrite.contains(message)) {
518  const auto parts = mHeaderOverwrite.value(message);
519  for (const auto &messagePart : parts) {
520  if (messagePart->hasHeader(header)) {
521  ret << messagePart;
522  }
523  }
524  }
525  return ret;
526 }
527 
528 QVector<KMime::Headers::Base *> NodeHelper::headers(const char *header, const KMime::Content *message)
529 {
530  const auto mp = messagePartsOfMailHeader(header, message);
531  if (mp.size() > 0) {
532  return mp.value(0)->headers(header);
533  }
534 
535  return message->headersByType(header);
536 }
537 
538 KMime::Headers::Base const *NodeHelper::mailHeaderAsBase(const char *header, const KMime::Content *message) const
539 {
540  if (mHeaderOverwrite.contains(message)) {
541  const auto parts = mHeaderOverwrite.value(message);
542  for (const auto &messagePart : parts) {
543  if (messagePart->hasHeader(header)) {
544  return messagePart->header(header); // Found.
545  }
546  }
547  }
548  return message->headerByType(header);
549 }
550 
551 QSharedPointer<KMime::Headers::Generics::AddressList> NodeHelper::mailHeaderAsAddressList(const char *header, const KMime::Content *message) const
552 {
553  const auto hrd = mailHeaderAsBase(header, message);
554  if (!hrd) {
555  return nullptr;
556  }
558  addressList->from7BitString(hrd->as7BitString(false));
559  return addressList;
560 }
561 
562 void NodeHelper::clearOverrideHeaders()
563 {
564  mHeaderOverwrite.clear();
565 }
566 
567 void NodeHelper::registerOverrideHeader(KMime::Content *message, MessagePart::Ptr part)
568 {
569  if (!mHeaderOverwrite.contains(message)) {
570  mHeaderOverwrite[message] = QVector<MessagePart::Ptr>();
571  }
572  mHeaderOverwrite[message].append(part);
573 }
574 
575 QDateTime NodeHelper::dateHeader(KMime::Content *message) const
576 {
577  const auto dateHeader = mailHeaderAsBase("date", message);
578  if (dateHeader != nullptr) {
579  return static_cast<const KMime::Headers::Date *>(dateHeader)->dateTime();
580  }
581  return {};
582 }
583 
584 void NodeHelper::setOverrideCodec(KMime::Content *node, const QTextCodec *codec)
585 {
586  if (!node) {
587  return;
588  }
589 
590  mOverrideCodecs[node] = codec;
591 }
592 
593 const QTextCodec *NodeHelper::codec(KMime::Content *node)
594 {
595  if (!node || !node->contentType()) {
596  return mLocalCodec;
597  }
598 
599  const QTextCodec *c = mOverrideCodecs.value(node, nullptr);
600  if (!c) {
601  // no override-codec set for this message, try the CT charset parameter:
602  QByteArray charset = node->contentType()->charset();
603 
604  // utf-8 is a superset of us-ascii, so we don't loose anything, if we it insead
605  // utf-8 is nowadays that widely, that it is a good guess to use it to fix issus with broken clients.
606  if (charset.toLower() == "us-ascii") {
607  charset = "utf-8";
608  }
609  c = codecForName(charset);
610  }
611  if (!c) {
612  // no charset means us-ascii (RFC 2045), so using local encoding should
613  // be okay
614  c = mLocalCodec;
615  }
616  return c;
617 }
618 
619 const QTextCodec *NodeHelper::codecForName(const QByteArray &_str)
620 {
621  if (_str.isEmpty()) {
622  return nullptr;
623  }
624  const QByteArray codecBy = _str.toLower();
625  auto codec = QTextCodec::codecForName(codecBy);
626  if (!codec) {
627  codec = QTextCodec::codecForLocale();
628  }
629  return codec;
630 }
631 
632 QString NodeHelper::fileName(const KMime::Content *node)
633 {
634  QString name = const_cast<KMime::Content *>(node)->contentDisposition()->filename();
635  if (name.isEmpty()) {
636  name = const_cast<KMime::Content *>(node)->contentType()->name();
637  }
638 
639  name = name.trimmed();
640  return name;
641 }
642 
643 // FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalBodyPartMemento replacement
644 Interface::BodyPartMemento *NodeHelper::bodyPartMemento(KMime::Content *node, const QByteArray &which) const
645 {
646  const QMap<QString, QMap<QByteArray, Interface::BodyPartMemento *>>::const_iterator nit = mBodyPartMementoMap.find(persistentIndex(node));
647  if (nit == mBodyPartMementoMap.end()) {
648  return nullptr;
649  }
651  return it != nit->end() ? it.value() : nullptr;
652 }
653 
654 // FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalSetBodyPartMemento replacement
655 void NodeHelper::setBodyPartMemento(KMime::Content *node, const QByteArray &which, Interface::BodyPartMemento *memento)
656 {
657  QMap<QByteArray, Interface::BodyPartMemento *> &mementos = mBodyPartMementoMap[persistentIndex(node)];
658 
659  const QByteArray whichLower = which.toLower();
661 
662  if (it != mementos.end() && it.key() == whichLower) {
663  delete it.value();
664  if (memento) {
665  it.value() = memento;
666  } else {
667  mementos.erase(it);
668  }
669  } else {
670  mementos.insert(whichLower, memento);
671  }
672 }
673 
674 bool NodeHelper::isNodeDisplayedEmbedded(KMime::Content *node) const
675 {
676  qCDebug(MIMETREEPARSER_LOG) << "IS NODE: " << mDisplayEmbeddedNodes.contains(node);
677  return mDisplayEmbeddedNodes.contains(node);
678 }
679 
680 void NodeHelper::setNodeDisplayedEmbedded(KMime::Content *node, bool displayedEmbedded)
681 {
682  qCDebug(MIMETREEPARSER_LOG) << "SET NODE: " << node << displayedEmbedded;
683  if (displayedEmbedded) {
684  mDisplayEmbeddedNodes.insert(node);
685  } else {
686  mDisplayEmbeddedNodes.remove(node);
687  }
688 }
689 
690 bool NodeHelper::isNodeDisplayedHidden(KMime::Content *node) const
691 {
692  return mDisplayHiddenNodes.contains(node);
693 }
694 
695 void NodeHelper::setNodeDisplayedHidden(KMime::Content *node, bool displayedHidden)
696 {
697  if (displayedHidden) {
698  mDisplayHiddenNodes.insert(node);
699  } else {
700  mDisplayEmbeddedNodes.remove(node);
701  }
702 }
703 
704 /*!
705  Creates a persistent index string that bridges the gap between the
706  permanent nodes and the temporary ones.
707 
708  Used internally for robust indexing.
709 */
710 QString NodeHelper::persistentIndex(const KMime::Content *node) const
711 {
712  if (!node) {
713  return {};
714  }
715 
716  QString indexStr = node->index().toString();
717  if (indexStr.isEmpty()) {
719  while (it.hasNext()) {
720  it.next();
721  const auto &extraNodes = it.value();
722  for (int i = 0; i < extraNodes.size(); ++i) {
723  if (extraNodes[i] == node) {
724  indexStr = QStringLiteral("e%1").arg(i);
725  const QString parentIndex = persistentIndex(it.key());
726  if (!parentIndex.isEmpty()) {
727  indexStr = QStringLiteral("%1:%2").arg(parentIndex, indexStr);
728  }
729  return indexStr;
730  }
731  }
732  }
733  } else {
734  const KMime::Content *const topLevel = node->topLevel();
735  // if the node is an extra node, prepend the index of the extra node to the url
737  while (it.hasNext()) {
738  it.next();
739  const QVector<KMime::Content *> &extraNodes = extraContents(it.key());
740  for (int i = 0; i < extraNodes.size(); ++i) {
741  KMime::Content *const extraNode = extraNodes[i];
742  if (topLevel == extraNode) {
743  indexStr.prepend(QStringLiteral("e%1:").arg(i));
744  const QString parentIndex = persistentIndex(it.key());
745  if (!parentIndex.isEmpty()) {
746  indexStr = QStringLiteral("%1:%2").arg(parentIndex, indexStr);
747  }
748  return indexStr;
749  }
750  }
751  }
752  }
753 
754  return indexStr;
755 }
756 
757 KMime::Content *NodeHelper::contentFromIndex(KMime::Content *node, const QString &persistentIndex) const
758 {
759  if (!node) {
760  return nullptr;
761  }
762  KMime::Content *c = node->topLevel();
763  if (c) {
764  const QStringList pathParts = persistentIndex.split(QLatin1Char(':'), Qt::SkipEmptyParts);
765  const int pathPartsSize(pathParts.size());
766  for (int i = 0; i < pathPartsSize; ++i) {
767  const QString &path = pathParts[i];
768  if (path.startsWith(QLatin1Char('e'))) {
769  const QVector<KMime::Content *> &extraParts = mExtraContents.value(c);
770  const int idx = QStringView(path).mid(1).toInt();
771  c = (idx < extraParts.size()) ? extraParts[idx] : nullptr;
772  } else {
773  c = c->content(KMime::ContentIndex(path));
774  }
775  if (!c) {
776  break;
777  }
778  }
779  }
780  return c;
781 }
782 
783 QString NodeHelper::asHREF(const KMime::Content *node, const QString &place) const
784 {
785  return QStringLiteral("attachment:%1?place=%2").arg(persistentIndex(node), place);
786 }
787 
788 QString NodeHelper::asHREF(const KMime::Content *node) const
789 {
790  return QStringLiteral("attachment:%1").arg(persistentIndex(node));
791 }
792 
793 KMime::Content *NodeHelper::fromHREF(const KMime::Message::Ptr &mMessage, const QUrl &url) const
794 {
795  if (url.isEmpty()) {
796  return mMessage.data();
797  }
798 
799  if (!url.isLocalFile()) {
800  return contentFromIndex(mMessage.data(), url.adjusted(QUrl::StripTrailingSlash).path());
801  } else {
802  const QString path = url.toLocalFile();
803  const QString extractedPath = extractAttachmentIndex(path);
804  if (!extractedPath.isEmpty()) {
805  return contentFromIndex(mMessage.data(), extractedPath);
806  }
807  return mMessage.data();
808  }
809 }
810 
811 QString NodeHelper::extractAttachmentIndex(const QString &path) const
812 {
813  // extract from /<path>/qttestn28554.index.2.3:0:2/unnamed -> "2.3:0:2"
814  // start of the index is something that is not a number followed by a dot: \D.
815  // index is only made of numbers,"." and ":": ([0-9.:]+)
816  // index is the last part of the folder name: /
817  static const QRegularExpression re(QStringLiteral("\\D\\.([e0-9.:]+)/"));
819  path.lastIndexOf(re, -1, &rmatch);
820  if (rmatch.hasMatch()) {
821  return rmatch.captured(1);
822  }
823  return {};
824 }
825 
826 QString NodeHelper::fixEncoding(const QString &encoding)
827 {
828  QString returnEncoding = encoding;
829  // According to https://www.iana.org/assignments/character-sets, uppercase is
830  // preferred in MIME headers
831  const QString returnEncodingToUpper = returnEncoding.toUpper();
832  if (returnEncodingToUpper.contains(QLatin1String("ISO "))) {
833  returnEncoding = returnEncodingToUpper;
834  returnEncoding.replace(QLatin1String("ISO "), QStringLiteral("ISO-"));
835  }
836  return returnEncoding;
837 }
838 
839 //-----------------------------------------------------------------------------
840 QString NodeHelper::encodingForName(const QString &descriptiveName)
841 {
842  const QString encoding = KCharsets::charsets()->encodingForName(descriptiveName);
843  return NodeHelper::fixEncoding(encoding);
844 }
845 
846 QStringList NodeHelper::supportedEncodings(bool usAscii)
847 {
849  QStringList encodings;
850  QMap<QString, bool> mimeNames;
851  QStringList::ConstIterator constEnd(encodingNames.constEnd());
852  for (QStringList::ConstIterator it = encodingNames.constBegin(); it != constEnd; ++it) {
853  QTextCodec *codec = QTextCodec::codecForName((*it).toLatin1());
854  const QString mimeName = (codec) ? QString::fromLatin1(codec->name()).toLower() : (*it);
855  if (!mimeNames.contains(mimeName)) {
857  mimeNames.insert(mimeName, true);
858  }
859  }
860  encodings.sort();
861  if (usAscii) {
862  encodings.prepend(KCharsets::charsets()->descriptionForEncoding(QStringLiteral("us-ascii")));
863  }
864  return encodings;
865 }
866 
867 QString NodeHelper::fromAsString(KMime::Content *node) const
868 {
869  if (auto topLevel = dynamic_cast<KMime::Message *>(node->topLevel())) {
870  return topLevel->from()->asUnicodeString();
871  } else {
872  auto realNode = std::find_if(mExtraContents.cbegin(), mExtraContents.cend(), [node](const QVector<KMime::Content *> &nodes) {
873  return nodes.contains(node);
874  });
875  if (realNode != mExtraContents.cend()) {
876  return fromAsString(realNode.key());
877  }
878  }
879 
880  return {};
881 }
882 
883 void NodeHelper::attachExtraContent(KMime::Content *topLevelNode, KMime::Content *content)
884 {
885  qCDebug(MIMETREEPARSER_LOG) << "mExtraContents added for" << topLevelNode << " extra content: " << content;
886  mExtraContents[topLevelNode].append(content);
887 }
888 
889 void NodeHelper::cleanExtraContent(KMime::Content *topLevelNode)
890 {
891  qCDebug(MIMETREEPARSER_LOG) << "remove all extraContents for" << topLevelNode;
892  mExtraContents[topLevelNode].clear();
893 }
894 
895 QVector<KMime::Content *> NodeHelper::extraContents(KMime::Content *topLevelnode) const
896 {
897  return mExtraContents.value(topLevelnode);
898 }
899 
900 void NodeHelper::mergeExtraNodes(KMime::Content *node)
901 {
902  if (!node) {
903  return;
904  }
905 
906  const QVector<KMime::Content *> extraNodes = extraContents(node);
907  for (KMime::Content *extra : extraNodes) {
908  if (node->bodyIsMessage()) {
909  qCWarning(MIMETREEPARSER_LOG) << "Asked to attach extra content to a kmime::message, this does not make sense. Attaching to:" << node
910  << node->encodedContent() << "\n====== with =======\n"
911  << extra << extra->encodedContent();
912  continue;
913  }
914  auto c = new KMime::Content(node);
915  c->setContent(extra->encodedContent());
916  c->parse();
917  node->addContent(c);
918  }
919 
920  const auto contents{node->contents()};
921  for (KMime::Content *child : contents) {
922  mergeExtraNodes(child);
923  }
924 }
925 
926 void NodeHelper::cleanFromExtraNodes(KMime::Content *node)
927 {
928  if (!node) {
929  return;
930  }
931  const QVector<KMime::Content *> extraNodes = extraContents(node);
932  for (KMime::Content *extra : extraNodes) {
933  QByteArray s = extra->encodedContent();
934  const auto children = node->contents();
935  for (KMime::Content *c : children) {
936  if (c->encodedContent() == s) {
937  node->removeContent(c);
938  }
939  }
940  }
941  const auto contents{node->contents()};
942  for (KMime::Content *child : contents) {
943  cleanFromExtraNodes(child);
944  }
945 }
946 
947 KMime::Message *NodeHelper::messageWithExtraContent(KMime::Content *topLevelNode)
948 {
949  /*The merge is done in several steps:
950  1) merge the extra nodes into topLevelNode
951  2) copy the modified (merged) node tree into a new node tree
952  3) restore the original node tree in topLevelNode by removing the extra nodes from it
953 
954  The reason is that extra nodes are assigned by pointer value to the nodes in the original tree.
955  */
956  if (!topLevelNode) {
957  return nullptr;
958  }
959 
960  mergeExtraNodes(topLevelNode);
961 
962  auto m = new KMime::Message;
963  m->setContent(topLevelNode->encodedContent());
964  m->parse();
965 
966  cleanFromExtraNodes(topLevelNode);
967  // qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITH EXTRA: " << m->encodedContent();
968  // qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITHOUT EXTRA: " << topLevelNode->encodedContent();
969 
970  return m;
971 }
972 
973 KMime::Content *NodeHelper::decryptedNodeForContent(KMime::Content *content) const
974 {
975  const QVector<KMime::Content *> xc = extraContents(content);
976  if (!xc.empty()) {
977  if (xc.size() == 1) {
978  return xc.front();
979  } else {
980  qCWarning(MIMETREEPARSER_LOG) << "WTF, encrypted node has multiple extra contents?";
981  }
982  }
983  return nullptr;
984 }
985 
986 bool NodeHelper::unencryptedMessage_helper(KMime::Content *node, QByteArray &resultingData, bool addHeaders, int recursionLevel)
987 {
988  bool returnValue = false;
989  if (node) {
990  KMime::Content *curNode = node;
991  KMime::Content *decryptedNode = nullptr;
992  const QByteArray type = node->contentType(false) ? QByteArray(node->contentType(false)->mediaType()).toLower() : "text";
993  const QByteArray subType = node->contentType(false) ? node->contentType(false)->subType().toLower() : "plain";
994  const bool isMultipart = node->contentType(false) && node->contentType(false)->isMultipart();
995  bool isSignature = false;
996 
997  qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") Looking at" << type << "/" << subType;
998 
999  if (isMultipart) {
1000  if (subType == "signed") {
1001  isSignature = true;
1002  } else if (subType == "encrypted") {
1003  decryptedNode = decryptedNodeForContent(curNode);
1004  }
1005  } else if (type == "application") {
1006  if (subType == "octet-stream") {
1007  decryptedNode = decryptedNodeForContent(curNode);
1008  } else if (subType == "pkcs7-signature") {
1009  isSignature = true;
1010  } else if (subType == "pkcs7-mime") {
1011  // note: subtype pkcs7-mime can also be signed
1012  // and we do NOT want to remove the signature!
1013  if (encryptionState(curNode) != KMMsgNotEncrypted) {
1014  decryptedNode = decryptedNodeForContent(curNode);
1015  }
1016  }
1017  }
1018 
1019  if (decryptedNode) {
1020  qCDebug(MIMETREEPARSER_LOG) << "Current node has an associated decrypted node, adding a modified header "
1021  "and then processing the children.";
1022 
1023  Q_ASSERT(addHeaders);
1024  KMime::Content headers;
1025  headers.setHead(curNode->head());
1026  headers.parse();
1027  if (auto ct = decryptedNode->contentType(false)) {
1028  headers.contentType()->from7BitString(ct->as7BitString(false));
1029  } else {
1031  }
1032  if (auto ct = decryptedNode->contentTransferEncoding(false)) {
1033  headers.contentTransferEncoding()->from7BitString(ct->as7BitString(false));
1034  } else {
1036  }
1037  if (auto cd = decryptedNode->contentDisposition(false)) {
1038  headers.contentDisposition()->from7BitString(cd->as7BitString(false));
1039  } else {
1041  }
1042  if (auto cd = decryptedNode->contentDescription(false)) {
1043  headers.contentDescription()->from7BitString(cd->as7BitString(false));
1044  } else {
1046  }
1047  headers.assemble();
1048 
1049  resultingData += headers.head() + '\n';
1050  unencryptedMessage_helper(decryptedNode, resultingData, false, recursionLevel + 1);
1051 
1052  returnValue = true;
1053  } else if (isSignature) {
1054  qCDebug(MIMETREEPARSER_LOG) << "Current node is a signature, adding it as-is.";
1055  // We can't change the nodes under the signature, as that would invalidate it. Add the signature
1056  // and its child as-is
1057  if (addHeaders) {
1058  resultingData += curNode->head() + '\n';
1059  }
1060  resultingData += curNode->encodedBody();
1061  returnValue = false;
1062  } else if (isMultipart) {
1063  qCDebug(MIMETREEPARSER_LOG) << "Current node is a multipart node, adding its header and then processing all children.";
1064  // Normal multipart node, add the header and all of its children
1065  bool somethingChanged = false;
1066  if (addHeaders) {
1067  resultingData += curNode->head() + '\n';
1068  }
1069  const QByteArray boundary = curNode->contentType()->boundary();
1070  const auto contents = curNode->contents();
1071  for (KMime::Content *child : contents) {
1072  resultingData += "\n--" + boundary + '\n';
1073  const bool changed = unencryptedMessage_helper(child, resultingData, true, recursionLevel + 1);
1074  if (changed) {
1075  somethingChanged = true;
1076  }
1077  }
1078  resultingData += "\n--" + boundary + "--\n\n";
1079  returnValue = somethingChanged;
1080  } else if (curNode->bodyIsMessage()) {
1081  qCDebug(MIMETREEPARSER_LOG) << "Current node is a message, adding the header and then processing the child.";
1082  if (addHeaders) {
1083  resultingData += curNode->head() + '\n';
1084  }
1085 
1086  returnValue = unencryptedMessage_helper(curNode->bodyAsMessage().data(), resultingData, true, recursionLevel + 1);
1087  } else {
1088  qCDebug(MIMETREEPARSER_LOG) << "Current node is an ordinary leaf node, adding it as-is.";
1089  if (addHeaders) {
1090  resultingData += curNode->head() + '\n';
1091  }
1092  resultingData += curNode->body();
1093  returnValue = false;
1094  }
1095  }
1096 
1097  qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") done.";
1098  return returnValue;
1099 }
1100 
1101 KMime::Message::Ptr NodeHelper::unencryptedMessage(const KMime::Message::Ptr &originalMessage)
1102 {
1103  QByteArray resultingData;
1104  const bool messageChanged = unencryptedMessage_helper(originalMessage.data(), resultingData, true);
1105  if (messageChanged) {
1106 #if 0
1107  qCDebug(MIMETREEPARSER_LOG) << "Resulting data is:" << resultingData;
1108  QFile bla("stripped.mbox");
1110  bla.write(resultingData);
1111  bla.close();
1112 #endif
1113  KMime::Message::Ptr newMessage(new KMime::Message);
1114  newMessage->setContent(resultingData);
1115  newMessage->parse();
1116  return newMessage;
1117  } else {
1118  return {};
1119  }
1120 }
1121 
1122 QVector<KMime::Content *> NodeHelper::attachmentsOfExtraContents() const
1123 {
1125  for (auto it = mExtraContents.begin(), end = mExtraContents.end(); it != end; ++it) {
1126  const auto contents = it.value();
1127  for (auto content : contents) {
1128  if (KMime::isAttachment(content)) {
1129  result.push_back(content);
1130  } else {
1131  result += content->attachments();
1132  }
1133  }
1134  }
1135  return result;
1136 }
1137 }
void append(const T &value)
Content * content(const ContentIndex &index) const
KIMAP2_EXPORT QTextCodec * codecForName(const QString &name)
QByteArray encodedBody()
QByteArray body() const
bool isNull() const const
QString errorString() const const
T * data() const const
bool removeHeader()
bool contains(const Key &key) const const
QByteArray toLower() const const
QString toUpper() const const
QString encodingForName(const QString &descriptiveName) const
QByteArray mimeType() const
Content * parent() const
virtual QFileDevice::Permissions permissions() const const override
virtual bool open(QIODevice::OpenMode mode) override
void clear()
const T value(const Key &key, const T &defaultValue) const const
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
void removeContent(Content *content, bool del=false)
bool bodyIsMessage() const
QMap::iterator begin()
void from7BitString(const char *s, size_t len) override
QString trimmed() const const
QStringView mid(qsizetype start) const const
void push_back(const T &value)
QString & prepend(QChar ch)
virtual bool setPermissions(QFileDevice::Permissions permissions) override
void setHead(const QByteArray &head)
QVector< KMime::Content * > List
ContentIndex index() const
QList::const_iterator constBegin() const const
QString toString() const
void setMimeType(const QByteArray &mimeType)
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QByteArray boundary() const
Content * topLevel() const
QByteArray mediaType() const
QTextCodec * codecForLocale()
QString tempPath()
QMimeType mimeTypeForData(const QByteArray &data) const const
T value(int i) const const
QMap::iterator insert(const Key &key, const T &value)
int size() const const
QMap::iterator end()
void prepend(const T &value)
KGuiItem clear()
QMap::iterator find(const Key &key)
bool isEmpty() const const
QByteArray encodedContent(bool useCrLf=false)
StripTrailingSlash
SkipEmptyParts
bool isEmpty() const const
QUrl fromLocalFile(const QString &localFile)
QMap::iterator erase(QMap::iterator pos)
QTextCodec * codecForName(const QByteArray &name)
Headers::ContentDisposition * contentDisposition(bool create=true)
Headers::ContentTransferEncoding * contentTransferEncoding(bool create=true)
QString toLocalFile() const const
bool hasMatch() const const
QByteArray head() const
QByteArray subType() const
QStringList availableEncodingNames() const
virtual void close() override
QMap::iterator lowerBound(const Key &key)
static QByteArray defaultCharset()
interface of classes that implement status for BodyPartFormatters.
Definition: bodypart.h:33
const Key key(const T &value, const Key &defaultKey) const const
QString & replace(int position, int n, QChar after)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
typedef ConstIterator
QByteArray decodedContent()
QSharedPointer< Message > bodyAsMessage() const
void addContent(Content *content, bool prepend=false)
void setContent(const QByteArray &s)
QByteArray charset() const
QByteArray as7BitString(bool withHeaderType=true) const override
QString descriptionForEncoding(const QString &encoding) const
bool isEmpty() const const
QList::const_iterator constEnd() const const
QString path(QUrl::ComponentFormattingOptions options) const const
static KCharsets * charsets()
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString path(const QString &relativePath)
QString fromLatin1(const char *str, int size)
contentDisposition
bool isLocalFile() const const
const QList< QKeySequence > & next()
QUrl adjusted(QUrl::FormattingOptions options) const const
virtual void from7BitString(const char *s, size_t len)
T & front()
int size() const const
Headers::ContentType * contentType(bool create=true)
QVector::const_iterator constBegin() const const
QVector< Content * > attachments()
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString mid(int position, int n) const const
QString message
const QList< QKeySequence > & end()
bool empty() const const
QString captured(int nth) const const
Headers::ContentDescription * contentDescription(bool create=true)
qint64 write(const char *data, qint64 maxSize)
void sort(Qt::CaseSensitivity cs)
virtual QByteArray name() const const=0
QVector< Content * > contents() const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon May 8 2023 04:02:34 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.