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) {
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 KMime::Content *NodeHelper::fromHREF(const KMime::Message::Ptr &mMessage, const QUrl &url) const
789 {
790  if (url.isEmpty()) {
791  return mMessage.data();
792  }
793 
794  if (!url.isLocalFile()) {
795  return contentFromIndex(mMessage.data(), url.adjusted(QUrl::StripTrailingSlash).path());
796  } else {
797  const QString path = url.toLocalFile();
798  const QString extractedPath = extractAttachmentIndex(path);
799  if (!extractedPath.isEmpty()) {
800  return contentFromIndex(mMessage.data(), extractedPath);
801  }
802  return mMessage.data();
803  }
804 }
805 
806 QString NodeHelper::extractAttachmentIndex(const QString &path) const
807 {
808  // extract from /<path>/qttestn28554.index.2.3:0:2/unnamed -> "2.3:0:2"
809  // start of the index is something that is not a number followed by a dot: \D.
810  // index is only made of numbers,"." and ":": ([0-9.:]+)
811  // index is the last part of the folder name: /
812  static const QRegularExpression re(QStringLiteral("\\D\\.([e0-9.:]+)/"));
814  path.lastIndexOf(re, -1, &rmatch);
815  if (rmatch.hasMatch()) {
816  return rmatch.captured(1);
817  }
818  return {};
819 }
820 
821 QString NodeHelper::fixEncoding(const QString &encoding)
822 {
823  QString returnEncoding = encoding;
824  // According to https://www.iana.org/assignments/character-sets, uppercase is
825  // preferred in MIME headers
826  const QString returnEncodingToUpper = returnEncoding.toUpper();
827  if (returnEncodingToUpper.contains(QLatin1String("ISO "))) {
828  returnEncoding = returnEncodingToUpper;
829  returnEncoding.replace(QLatin1String("ISO "), QStringLiteral("ISO-"));
830  }
831  return returnEncoding;
832 }
833 
834 //-----------------------------------------------------------------------------
835 QString NodeHelper::encodingForName(const QString &descriptiveName)
836 {
837  const QString encoding = KCharsets::charsets()->encodingForName(descriptiveName);
838  return NodeHelper::fixEncoding(encoding);
839 }
840 
841 QStringList NodeHelper::supportedEncodings(bool usAscii)
842 {
844  QStringList encodings;
845  QMap<QString, bool> mimeNames;
846  QStringList::ConstIterator constEnd(encodingNames.constEnd());
847  for (QStringList::ConstIterator it = encodingNames.constBegin(); it != constEnd; ++it) {
848  QTextCodec *codec = QTextCodec::codecForName((*it).toLatin1());
849  const QString mimeName = (codec) ? QString::fromLatin1(codec->name()).toLower() : (*it);
850  if (!mimeNames.contains(mimeName)) {
852  mimeNames.insert(mimeName, true);
853  }
854  }
855  encodings.sort();
856  if (usAscii) {
857  encodings.prepend(KCharsets::charsets()->descriptionForEncoding(QStringLiteral("us-ascii")));
858  }
859  return encodings;
860 }
861 
862 QString NodeHelper::fromAsString(KMime::Content *node) const
863 {
864  if (auto topLevel = dynamic_cast<KMime::Message *>(node->topLevel())) {
865  return topLevel->from()->asUnicodeString();
866  } else {
867  auto realNode = std::find_if(mExtraContents.cbegin(), mExtraContents.cend(), [node](const QVector<KMime::Content *> &nodes) {
868  return nodes.contains(node);
869  });
870  if (realNode != mExtraContents.cend()) {
871  return fromAsString(realNode.key());
872  }
873  }
874 
875  return {};
876 }
877 
878 void NodeHelper::attachExtraContent(KMime::Content *topLevelNode, KMime::Content *content)
879 {
880  qCDebug(MIMETREEPARSER_LOG) << "mExtraContents added for" << topLevelNode << " extra content: " << content;
881  mExtraContents[topLevelNode].append(content);
882 }
883 
884 void NodeHelper::cleanExtraContent(KMime::Content *topLevelNode)
885 {
886  qCDebug(MIMETREEPARSER_LOG) << "remove all extraContents for" << topLevelNode;
887  mExtraContents[topLevelNode].clear();
888 }
889 
890 QVector<KMime::Content *> NodeHelper::extraContents(KMime::Content *topLevelnode) const
891 {
892  return mExtraContents.value(topLevelnode);
893 }
894 
895 void NodeHelper::mergeExtraNodes(KMime::Content *node)
896 {
897  if (!node) {
898  return;
899  }
900 
901  const QVector<KMime::Content *> extraNodes = extraContents(node);
902  for (KMime::Content *extra : extraNodes) {
903  if (node->bodyIsMessage()) {
904  qCWarning(MIMETREEPARSER_LOG) << "Asked to attach extra content to a kmime::message, this does not make sense. Attaching to:" << node
905  << node->encodedContent() << "\n====== with =======\n"
906  << extra << extra->encodedContent();
907  continue;
908  }
909  auto c = new KMime::Content(node);
910  c->setContent(extra->encodedContent());
911  c->parse();
912  node->addContent(c);
913  }
914 
915  const auto contents{node->contents()};
916  for (KMime::Content *child : contents) {
917  mergeExtraNodes(child);
918  }
919 }
920 
921 void NodeHelper::cleanFromExtraNodes(KMime::Content *node)
922 {
923  if (!node) {
924  return;
925  }
926  const QVector<KMime::Content *> extraNodes = extraContents(node);
927  for (KMime::Content *extra : extraNodes) {
928  QByteArray s = extra->encodedContent();
929  const auto children = node->contents();
930  for (KMime::Content *c : children) {
931  if (c->encodedContent() == s) {
932  node->removeContent(c);
933  }
934  }
935  }
936  const auto contents{node->contents()};
937  for (KMime::Content *child : contents) {
938  cleanFromExtraNodes(child);
939  }
940 }
941 
942 KMime::Message *NodeHelper::messageWithExtraContent(KMime::Content *topLevelNode)
943 {
944  /*The merge is done in several steps:
945  1) merge the extra nodes into topLevelNode
946  2) copy the modified (merged) node tree into a new node tree
947  3) restore the original node tree in topLevelNode by removing the extra nodes from it
948 
949  The reason is that extra nodes are assigned by pointer value to the nodes in the original tree.
950  */
951  if (!topLevelNode) {
952  return nullptr;
953  }
954 
955  mergeExtraNodes(topLevelNode);
956 
957  auto m = new KMime::Message;
958  m->setContent(topLevelNode->encodedContent());
959  m->parse();
960 
961  cleanFromExtraNodes(topLevelNode);
962  // qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITH EXTRA: " << m->encodedContent();
963  // qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITHOUT EXTRA: " << topLevelNode->encodedContent();
964 
965  return m;
966 }
967 
968 KMime::Content *NodeHelper::decryptedNodeForContent(KMime::Content *content) const
969 {
970  const QVector<KMime::Content *> xc = extraContents(content);
971  if (!xc.empty()) {
972  if (xc.size() == 1) {
973  return xc.front();
974  } else {
975  qCWarning(MIMETREEPARSER_LOG) << "WTF, encrypted node has multiple extra contents?";
976  }
977  }
978  return nullptr;
979 }
980 
981 bool NodeHelper::unencryptedMessage_helper(KMime::Content *node, QByteArray &resultingData, bool addHeaders, int recursionLevel)
982 {
983  bool returnValue = false;
984  if (node) {
985  KMime::Content *curNode = node;
986  KMime::Content *decryptedNode = nullptr;
987  const QByteArray type = node->contentType(false) ? QByteArray(node->contentType(false)->mediaType()).toLower() : "text";
988  const QByteArray subType = node->contentType(false) ? node->contentType(false)->subType().toLower() : "plain";
989  const bool isMultipart = node->contentType(false) && node->contentType(false)->isMultipart();
990  bool isSignature = false;
991 
992  qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") Looking at" << type << "/" << subType;
993 
994  if (isMultipart) {
995  if (subType == "signed") {
996  isSignature = true;
997  } else if (subType == "encrypted") {
998  decryptedNode = decryptedNodeForContent(curNode);
999  }
1000  } else if (type == "application") {
1001  if (subType == "octet-stream") {
1002  decryptedNode = decryptedNodeForContent(curNode);
1003  } else if (subType == "pkcs7-signature") {
1004  isSignature = true;
1005  } else if (subType == "pkcs7-mime") {
1006  // note: subtype pkcs7-mime can also be signed
1007  // and we do NOT want to remove the signature!
1008  if (encryptionState(curNode) != KMMsgNotEncrypted) {
1009  decryptedNode = decryptedNodeForContent(curNode);
1010  }
1011  }
1012  }
1013 
1014  if (decryptedNode) {
1015  qCDebug(MIMETREEPARSER_LOG) << "Current node has an associated decrypted node, adding a modified header "
1016  "and then processing the children.";
1017 
1018  Q_ASSERT(addHeaders);
1019  KMime::Content headers;
1020  headers.setHead(curNode->head());
1021  headers.parse();
1022  if (auto ct = decryptedNode->contentType(false)) {
1023  headers.contentType()->from7BitString(ct->as7BitString(false));
1024  } else {
1026  }
1027  if (auto ct = decryptedNode->contentTransferEncoding(false)) {
1028  headers.contentTransferEncoding()->from7BitString(ct->as7BitString(false));
1029  } else {
1031  }
1032  if (auto cd = decryptedNode->contentDisposition(false)) {
1033  headers.contentDisposition()->from7BitString(cd->as7BitString(false));
1034  } else {
1036  }
1037  if (auto cd = decryptedNode->contentDescription(false)) {
1038  headers.contentDescription()->from7BitString(cd->as7BitString(false));
1039  } else {
1041  }
1042  headers.assemble();
1043 
1044  resultingData += headers.head() + '\n';
1045  unencryptedMessage_helper(decryptedNode, resultingData, false, recursionLevel + 1);
1046 
1047  returnValue = true;
1048  } else if (isSignature) {
1049  qCDebug(MIMETREEPARSER_LOG) << "Current node is a signature, adding it as-is.";
1050  // We can't change the nodes under the signature, as that would invalidate it. Add the signature
1051  // and its child as-is
1052  if (addHeaders) {
1053  resultingData += curNode->head() + '\n';
1054  }
1055  resultingData += curNode->encodedBody();
1056  returnValue = false;
1057  } else if (isMultipart) {
1058  qCDebug(MIMETREEPARSER_LOG) << "Current node is a multipart node, adding its header and then processing all children.";
1059  // Normal multipart node, add the header and all of its children
1060  bool somethingChanged = false;
1061  if (addHeaders) {
1062  resultingData += curNode->head() + '\n';
1063  }
1064  const QByteArray boundary = curNode->contentType()->boundary();
1065  const auto contents = curNode->contents();
1066  for (KMime::Content *child : contents) {
1067  resultingData += "\n--" + boundary + '\n';
1068  const bool changed = unencryptedMessage_helper(child, resultingData, true, recursionLevel + 1);
1069  if (changed) {
1070  somethingChanged = true;
1071  }
1072  }
1073  resultingData += "\n--" + boundary + "--\n\n";
1074  returnValue = somethingChanged;
1075  } else if (curNode->bodyIsMessage()) {
1076  qCDebug(MIMETREEPARSER_LOG) << "Current node is a message, adding the header and then processing the child.";
1077  if (addHeaders) {
1078  resultingData += curNode->head() + '\n';
1079  }
1080 
1081  returnValue = unencryptedMessage_helper(curNode->bodyAsMessage().data(), resultingData, true, recursionLevel + 1);
1082  } else {
1083  qCDebug(MIMETREEPARSER_LOG) << "Current node is an ordinary leaf node, adding it as-is.";
1084  if (addHeaders) {
1085  resultingData += curNode->head() + '\n';
1086  }
1087  resultingData += curNode->body();
1088  returnValue = false;
1089  }
1090  }
1091 
1092  qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") done.";
1093  return returnValue;
1094 }
1095 
1096 KMime::Message::Ptr NodeHelper::unencryptedMessage(const KMime::Message::Ptr &originalMessage)
1097 {
1098  QByteArray resultingData;
1099  const bool messageChanged = unencryptedMessage_helper(originalMessage.data(), resultingData, true);
1100  if (messageChanged) {
1101 #if 0
1102  qCDebug(MIMETREEPARSER_LOG) << "Resulting data is:" << resultingData;
1103  QFile bla("stripped.mbox");
1105  bla.write(resultingData);
1106  bla.close();
1107 #endif
1108  KMime::Message::Ptr newMessage(new KMime::Message);
1109  newMessage->setContent(resultingData);
1110  newMessage->parse();
1111  return newMessage;
1112  } else {
1113  return {};
1114  }
1115 }
1116 
1117 QVector<KMime::Content *> NodeHelper::attachmentsOfExtraContents() const
1118 {
1120  for (auto it = mExtraContents.begin(), end = mExtraContents.end(); it != end; ++it) {
1121  const auto contents = it.value();
1122  for (auto content : contents) {
1123  if (KMime::isAttachment(content)) {
1124  result.push_back(content);
1125  } else {
1126  result += content->attachments();
1127  }
1128  }
1129  }
1130  return result;
1131 }
1132 }
void append(const T &value)
Content * content(const ContentIndex &index) const
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 fromLatin1(const char *str, int size)
contentDisposition
bool isLocalFile() const const
const QList< QKeySequence > & next()
KIMAP_EXPORT QTextCodec * codecForName(const QString &name)
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 Sat Jan 28 2023 04:09:55 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.