nodehelper.cpp 36.3 KB
Newer Older
1
/*
2
3
  SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
  SPDX-FileCopyrightText: 2009 Andras Mantia <andras@kdab.net>
4

5
  SPDX-License-Identifier: GPL-2.0-or-later
6
7
8
*/

#include "nodehelper.h"
Laurent Montel's avatar
Laurent Montel committed
9
10
#include "interfaces/bodypart.h"
#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>
Laurent Montel's avatar
Laurent Montel committed
18
19
#include <KLocalizedString>
#include <QTemporaryFile>
20
21

#include <QDir>
Laurent Montel's avatar
Laurent Montel committed
22
#include <QRegularExpressionMatch>
Laurent Montel's avatar
Laurent Montel committed
23
24
#include <QTextCodec>
#include <QUrl>
25

Laurent Montel's avatar
Laurent Montel committed
26
#include <QFileDevice>
27
28
#include <QMimeDatabase>
#include <QMimeType>
Laurent Montel's avatar
Laurent Montel committed
29
30
31
#include <algorithm>
#include <sstream>
#include <string>
32

Laurent Montel's avatar
Laurent Montel committed
33
34
namespace MimeTreeParser
{
Laurent Montel's avatar
Laurent Montel committed
35
36
NodeHelper::NodeHelper()
    : mAttachmentFilesDir(new AttachmentTemporaryFilesDirs())
37
{
38
    mListAttachmentTemporaryDirs.append(mAttachmentFilesDir);
Laurent Montel's avatar
Laurent Montel committed
39
    // TODO(Andras) add methods to modify these prefixes
40
41
42
43
44
45
46
47
48
49
50
51

    mLocalCodec = QTextCodec::codecForLocale();

    // In the case of Japan. Japanese locale name is "eucjp" but
    // The Japanese mail systems normally used "iso-2022-jp" of locale name.
    // We want to change locale name from eucjp to iso-2022-jp at KMail only.

    // (Introduction to i18n, 6.6 Limit of Locale technology):
    // EUC-JP is the de-facto standard for UNIX systems, ISO 2022-JP
    // is the standard for Internet, and Shift-JIS is the encoding
    // for Windows and Macintosh.
    if (mLocalCodec) {
Laurent Montel's avatar
Laurent Montel committed
52
53
        const QByteArray codecNameLower = mLocalCodec->name().toLower();
        if (codecNameLower == "eucjp"
54
#if defined Q_OS_WIN || defined Q_OS_MACX
Laurent Montel's avatar
Laurent Montel committed
55
            || codecNameLower == "shift-jis" // OK?
56
#endif
Laurent Montel's avatar
Laurent Montel committed
57
        ) {
58
59
60
61
62
63
64
65
66
67
            mLocalCodec = QTextCodec::codecForName("jis7");
            // QTextCodec *cdc = QTextCodec::codecForName("jis7");
            // QTextCodec::setCodecForLocale(cdc);
            // KLocale::global()->setEncoding(cdc->mibEnum());
        }
    }
}

NodeHelper::~NodeHelper()
{
68
69
70
71
72
    for (auto att : mListAttachmentTemporaryDirs) {
        if (att) {
            att->forceCleanTempFiles();
            delete att;
        }
73
    }
74
    clear();
75
76
77
78
79
80
81
82
}

void NodeHelper::setNodeProcessed(KMime::Content *node, bool recurse)
{
    if (!node) {
        return;
    }
    mProcessedNodes.append(node);
83
    qCDebug(MIMETREEPARSER_LOG) << "Node processed: " << node->index().toString() << node->contentType()->as7BitString();
84
85
    //<< " decodedContent" << node->decodedContent();
    if (recurse) {
Laurent Montel's avatar
Laurent Montel committed
86
87
        const auto contents = node->contents();
        for (KMime::Content *c : contents) {
88
89
90
91
92
93
94
95
96
97
98
99
            setNodeProcessed(c, true);
        }
    }
}

void NodeHelper::setNodeUnprocessed(KMime::Content *node, bool recurse)
{
    if (!node) {
        return;
    }
    mProcessedNodes.removeAll(node);

Laurent Montel's avatar
Laurent Montel committed
100
101
    // avoid double addition of extra nodes, eg. encrypted attachments
    const QMap<KMime::Content *, QVector<KMime::Content *>>::iterator it = mExtraContents.find(node);
102
103
104
105
106
107
108
109
    if (it != mExtraContents.end()) {
        Q_FOREACH (KMime::Content *c, it.value()) {
            KMime::Content *p = c->parent();
            if (p) {
                p->removeContent(c);
            }
        }
        qDeleteAll(it.value());
110
        qCDebug(MIMETREEPARSER_LOG) << "mExtraContents deleted for" << it.key();
111
112
113
        mExtraContents.erase(it);
    }

114
    qCDebug(MIMETREEPARSER_LOG) << "Node UNprocessed: " << node;
115
    if (recurse) {
Laurent Montel's avatar
Laurent Montel committed
116
117
        const auto contents = node->contents();
        for (KMime::Content *c : contents) {
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
            setNodeUnprocessed(c, true);
        }
    }
}

bool NodeHelper::nodeProcessed(KMime::Content *node) const
{
    if (!node) {
        return true;
    }
    return mProcessedNodes.contains(node);
}

static void clearBodyPartMemento(QMap<QByteArray, Interface::BodyPartMemento *> &bodyPartMementoMap)
{
Laurent Montel's avatar
Laurent Montel committed
133
    for (QMap<QByteArray, Interface::BodyPartMemento *>::iterator it = bodyPartMementoMap.begin(), end = bodyPartMementoMap.end(); it != end; ++it) {
134
135
136
137
138
139
140
141
142
143
144
145
146
        Interface::BodyPartMemento *memento = it.value();
        memento->detach();
        delete memento;
    }
    bodyPartMementoMap.clear();
}

void NodeHelper::clear()
{
    mProcessedNodes.clear();
    mEncryptionState.clear();
    mSignatureState.clear();
    mOverrideCodecs.clear();
Laurent Montel's avatar
Laurent Montel committed
147
    std::for_each(mBodyPartMementoMap.begin(), mBodyPartMementoMap.end(), &clearBodyPartMemento);
148
    mBodyPartMementoMap.clear();
Laurent Montel's avatar
Laurent Montel committed
149
    QMap<KMime::Content *, QVector<KMime::Content *>>::ConstIterator end(mExtraContents.constEnd());
150

Laurent Montel's avatar
Laurent Montel committed
151
    for (QMap<KMime::Content *, QVector<KMime::Content *>>::ConstIterator it = mExtraContents.constBegin(); it != end; ++it) {
152
153
154
155
156
157
158
        Q_FOREACH (KMime::Content *c, it.value()) {
            KMime::Content *p = c->parent();
            if (p) {
                p->removeContent(c);
            }
        }
        qDeleteAll(it.value());
159
        qCDebug(MIMETREEPARSER_LOG) << "mExtraContents deleted for" << it.key();
160
161
162
163
164
165
    }
    mExtraContents.clear();
    mDisplayEmbeddedNodes.clear();
    mDisplayHiddenNodes.clear();
}

Sandro Knauß's avatar
costify    
Sandro Knauß committed
166
void NodeHelper::setEncryptionState(const KMime::Content *node, const KMMsgEncryptionState state)
167
168
169
170
{
    mEncryptionState[node] = state;
}

171
KMMsgEncryptionState NodeHelper::encryptionState(const KMime::Content *node) const
172
173
174
175
{
    return mEncryptionState.value(node, KMMsgNotEncrypted);
}

Sandro Knauß's avatar
costify    
Sandro Knauß committed
176
void NodeHelper::setSignatureState(const KMime::Content *node, const KMMsgSignatureState state)
177
178
179
180
{
    mSignatureState[node] = state;
}

181
KMMsgSignatureState NodeHelper::signatureState(const KMime::Content *node) const
182
183
184
185
186
187
188
189
190
191
192
193
194
195
{
    return mSignatureState.value(node, KMMsgNotSigned);
}

PartMetaData NodeHelper::partMetaData(KMime::Content *node)
{
    return mPartMetaDatas.value(node, PartMetaData());
}

void NodeHelper::setPartMetaData(KMime::Content *node, const PartMetaData &metaData)
{
    mPartMetaDatas.insert(node, metaData);
}

196
197
198
199
200
201
202
203
204
205
QString NodeHelper::writeFileToTempFile(KMime::Content *node, const QString &filename)
{
    QString fname = createTempDir(persistentIndex(node));
    if (fname.isEmpty()) {
        return QString();
    }
    fname += QLatin1Char('/') + filename;
    QFile f(fname);
    if (!f.open(QIODevice::ReadWrite)) {
        qCWarning(MIMETREEPARSER_LOG) << "Failed to write note to file:" << f.errorString();
206
        mAttachmentFilesDir->addTempFile(fname);
207
208
209
210
211
212
213
214
215
216
217
218
        return QString();
    }
    f.write(QByteArray());
    mAttachmentFilesDir->addTempFile(fname);
    // make file read-only so that nobody gets the impression that he might
    // edit attached files (cf. bug #52813)
    f.setPermissions(QFileDevice::ReadUser);
    f.close();

    return fname;
}

219
220
221
222
223
QString NodeHelper::writeNodeToTempFile(KMime::Content *node)
{
    // If the message part is already written to a file, no point in doing it again.
    // This function is called twice actually, once from the rendering of the attachment
    // in the body and once for the header.
Laurent Montel's avatar
Laurent Montel committed
224
    const QUrl existingFileName = tempFileUrlFromNode(node);
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
    if (!existingFileName.isEmpty()) {
        return existingFileName.toLocalFile();
    }

    QString fname = createTempDir(persistentIndex(node));
    if (fname.isEmpty()) {
        return QString();
    }

    QString fileName = NodeHelper::fileName(node);
    // strip off a leading path
    int slashPos = fileName.lastIndexOf(QLatin1Char('/'));
    if (-1 != slashPos) {
        fileName = fileName.mid(slashPos + 1);
    }
    if (fileName.isEmpty()) {
        fileName = QStringLiteral("unnamed");
    }
    fname += QLatin1Char('/') + fileName;

245
    qCDebug(MIMETREEPARSER_LOG) << "Create temp file: " << fname;
246
    QByteArray data = node->decodedContent();
Laurent Montel's avatar
Laurent Montel committed
247
    if (node->contentType()->isText() && !data.isEmpty()) {
248
249
250
251
252
        // convert CRLF to LF before writing text attachments to disk
        data = KMime::CRLFtoLF(data);
    }
    QFile f(fname);
    if (!f.open(QIODevice::ReadWrite)) {
253
        qCWarning(MIMETREEPARSER_LOG) << "Failed to write note to file:" << f.errorString();
254
        mAttachmentFilesDir->addTempFile(fname);
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
        return QString();
    }
    f.write(data);
    mAttachmentFilesDir->addTempFile(fname);
    // make file read-only so that nobody gets the impression that he might
    // edit attached files (cf. bug #52813)
    f.setPermissions(QFileDevice::ReadUser);
    f.close();

    return fname;
}

QUrl NodeHelper::tempFileUrlFromNode(const KMime::Content *node)
{
    if (!node) {
        return QUrl();
    }

    const QString index = persistentIndex(node);

Laurent Montel's avatar
Laurent Montel committed
275
276
    const QStringList temporaryFiles = mAttachmentFilesDir->temporaryFiles();
    for (const QString &path : temporaryFiles) {
277
278
279
280
281
282
        const int right = path.lastIndexOf(QLatin1Char('/'));
        int left = path.lastIndexOf(QLatin1String(".index."), right);
        if (left != -1) {
            left += 7;
        }

Laurent Montel's avatar
Laurent Montel committed
283
        const QStringRef storedIndex(&path, left, right - left);
284
285
286
287
288
289
290
291
292
        if (left != -1 && storedIndex == index) {
            return QUrl::fromLocalFile(path);
        }
    }
    return QUrl();
}

QString NodeHelper::createTempDir(const QString &param)
{
Laurent Montel's avatar
Laurent Montel committed
293
    auto tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/messageviewer_XXXXXX") + QLatin1String(".index.") + param);
294
    tempFile->open();
295
    const QString fname = tempFile->fileName();
296
297
    delete tempFile;

298
299
    QFile fFile(fname);
    if (!(fFile.permissions() & QFileDevice::WriteUser)) {
300
        // Not there or not writable
Laurent Montel's avatar
Laurent Montel committed
301
        if (!QDir().mkpath(fname) || !fFile.setPermissions(QFileDevice::WriteUser | QFileDevice::ReadUser | QFileDevice::ExeUser)) {
302
            mAttachmentFilesDir->addTempDir(fname);
Laurent Montel's avatar
Laurent Montel committed
303
            return QString(); // failed create
304
305
306
307
308
309
310
311
312
313
314
315
316
        }
    }

    Q_ASSERT(!fname.isNull());

    mAttachmentFilesDir->addTempDir(fname);
    return fname;
}

void NodeHelper::forceCleanTempFiles()
{
    mAttachmentFilesDir->forceCleanTempFiles();
    delete mAttachmentFilesDir;
Laurent Montel's avatar
Laurent Montel committed
317
    mAttachmentFilesDir = nullptr;
318
319
320
321
}

void NodeHelper::removeTempFiles()
{
Laurent Montel's avatar
Laurent Montel committed
322
    // Don't delete as it will be deleted in class
Laurent Montel's avatar
Laurent Montel committed
323
324
325
    if (mAttachmentFilesDir) {
        mAttachmentFilesDir->removeTempFiles();
    }
326
    mAttachmentFilesDir = new AttachmentTemporaryFilesDirs();
327
    mListAttachmentTemporaryDirs.append(mAttachmentFilesDir);
328
329
330
331
332
333
334
335
336
337
338
339
}

void NodeHelper::addTempFile(const QString &file)
{
    mAttachmentFilesDir->addTempFile(file);
}

bool NodeHelper::isInEncapsulatedMessage(KMime::Content *node)
{
    const KMime::Content *const topLevel = node->topLevel();
    const KMime::Content *cur = node;
    while (cur && cur != topLevel) {
Laurent Montel's avatar
Laurent Montel committed
340
341
        const bool parentIsMessage =
            cur->parent() && cur->parent()->contentType(false) && cur->parent()->contentType(false)->mimeType().toLower() == "message/rfc822";
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
        if (parentIsMessage && cur->parent() != topLevel) {
            return true;
        }
        cur = cur->parent();
    }
    return false;
}

QByteArray NodeHelper::charset(KMime::Content *node)
{
    if (node->contentType(false)) {
        return node->contentType(false)->charset();
    } else {
        return node->defaultCharset();
    }
}

KMMsgEncryptionState NodeHelper::overallEncryptionState(KMime::Content *node) const
{
    KMMsgEncryptionState myState = KMMsgEncryptionStateUnknown;
    if (!node) {
        return myState;
    }

366
    KMime::Content *parent = node->parent();
Laurent Montel's avatar
Laurent Montel committed
367
    auto contents = parent ? parent->contents() : KMime::Content::List();
368
369
370
371
    if (contents.isEmpty()) {
        contents.append(node);
    }
    int i = contents.indexOf(const_cast<KMime::Content *>(node));
372
373
374
    if (i < 0) {
        return myState;
    }
375
376
377
378
    for (; i < contents.size(); ++i) {
        auto next = contents.at(i);
        KMMsgEncryptionState otherState = encryptionState(next);

379
        // NOTE: children are tested ONLY when parent is not encrypted
380
381
        if (otherState == KMMsgNotEncrypted && !next->contents().isEmpty()) {
            otherState = overallEncryptionState(next->contents().at(0));
382
        }
383

384
385
386
387
        if (otherState == KMMsgNotEncrypted && !extraContents(next).isEmpty()) {
            otherState = overallEncryptionState(extraContents(next).at(0));
        }

388
389
390
391
        if (next == node) {
            myState = otherState;
        }

392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
        switch (otherState) {
        case KMMsgEncryptionStateUnknown:
            break;
        case KMMsgNotEncrypted:
            if (myState == KMMsgFullyEncrypted) {
                myState = KMMsgPartiallyEncrypted;
            } else if (myState != KMMsgPartiallyEncrypted) {
                myState = KMMsgNotEncrypted;
            }
            break;
        case KMMsgPartiallyEncrypted:
            myState = KMMsgPartiallyEncrypted;
            break;
        case KMMsgFullyEncrypted:
            if (myState != KMMsgFullyEncrypted) {
                myState = KMMsgPartiallyEncrypted;
            }
            break;
        case KMMsgEncryptionProblematic:
            break;
        }
    }

415
    qCDebug(MIMETREEPARSER_LOG) << "\n\n  KMMsgEncryptionState:" << myState;
416
417
418
419
420
421
422
423
424
425
426

    return myState;
}

KMMsgSignatureState NodeHelper::overallSignatureState(KMime::Content *node) const
{
    KMMsgSignatureState myState = KMMsgSignatureStateUnknown;
    if (!node) {
        return myState;
    }

427
    KMime::Content *parent = node->parent();
Laurent Montel's avatar
Laurent Montel committed
428
    auto contents = parent ? parent->contents() : KMime::Content::List();
429
430
    if (contents.isEmpty()) {
        contents.append(node);
431
    }
432
    int i = contents.indexOf(const_cast<KMime::Content *>(node));
Laurent Montel's avatar
Laurent Montel committed
433
    if (i < 0) { // Be safe
Laurent Montel's avatar
be safe    
Laurent Montel committed
434
435
        return myState;
    }
436
437
438
439
440
441
442
443
444
    for (; i < contents.size(); ++i) {
        auto next = contents.at(i);
        KMMsgSignatureState otherState = signatureState(next);

        // NOTE: children are tested ONLY when parent is not encrypted
        if (otherState == KMMsgNotSigned && !next->contents().isEmpty()) {
            otherState = overallSignatureState(next->contents().at(0));
        }

445
446
447
448
        if (otherState == KMMsgNotSigned && !extraContents(next).isEmpty()) {
            otherState = overallSignatureState(extraContents(next).at(0));
        }

449
450
451
452
        if (next == node) {
            myState = otherState;
        }

453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
        switch (otherState) {
        case KMMsgSignatureStateUnknown:
            break;
        case KMMsgNotSigned:
            if (myState == KMMsgFullySigned) {
                myState = KMMsgPartiallySigned;
            } else if (myState != KMMsgPartiallySigned) {
                myState = KMMsgNotSigned;
            }
            break;
        case KMMsgPartiallySigned:
            myState = KMMsgPartiallySigned;
            break;
        case KMMsgFullySigned:
            if (myState != KMMsgFullySigned) {
                myState = KMMsgPartiallySigned;
            }
            break;
        case KMMsgSignatureProblematic:
            break;
        }
    }

476
    qCDebug(MIMETREEPARSER_LOG) << "\n\n  KMMsgSignatureState:" << myState;
477
478
479
480
481
482

    return myState;
}

void NodeHelper::magicSetType(KMime::Content *node, bool aAutoDecode)
{
Laurent Montel's avatar
Laurent Montel committed
483
    const QByteArray body = aAutoDecode ? node->decodedContent() : node->body();
484
    QMimeDatabase db;
Laurent Montel's avatar
Laurent Montel committed
485
    const QMimeType mime = db.mimeTypeForData(body);
486

Laurent Montel's avatar
Laurent Montel committed
487
    const QString mimetype = mime.name();
488
489
490
    node->contentType()->setMimeType(mimetype.toLatin1());
}

491
bool NodeHelper::hasMailHeader(const char *header, const KMime::Content *message) const
492
{
493
494
495
496
497
498
499
    if (mHeaderOverwrite.contains(message)) {
        foreach (const auto messagePart, mHeaderOverwrite.value(message)) {
            if (messagePart->hasHeader(header)) {
                return true;
            }
        }
    }
500
501
502
    return message->hasHeader(header);
}

Laurent Montel's avatar
Laurent Montel committed
503
KMime::Headers::Base const *NodeHelper::mailHeaderAsBase(const char *header, const KMime::Content *message) const
504
{
505
506
507
508
509
510
511
    if (mHeaderOverwrite.contains(message)) {
        foreach (const auto messagePart, mHeaderOverwrite.value(message)) {
            if (messagePart->hasHeader(header)) {
                return messagePart->header(header); // Found.
            }
        }
    }
512
513
514
    return message->headerByType(header);
}

Laurent Montel's avatar
Laurent Montel committed
515
QSharedPointer<KMime::Headers::Generics::AddressList> NodeHelper::mailHeaderAsAddressList(const char *header, const KMime::Content *message) const
516
{
Laurent Montel's avatar
Laurent Montel committed
517
    QSharedPointer<KMime::Headers::Generics::AddressList> addressList(new KMime::Headers::Generics::AddressList());
518
    const auto hrd = mailHeaderAsBase(header, message);
519
520
521
522
523
    const QByteArray &data = hrd->as7BitString(false);
    addressList->from7BitString(data);
    return addressList;
}

524
525
526
527
528
529
void NodeHelper::clearOverrideHeaders()
{
    mHeaderOverwrite.clear();
}

void NodeHelper::registerOverrideHeader(KMime::Content *message, MessagePart::Ptr part)
530
{
531
532
533
534
535
536
537
538
539
540
541
542
543
    if (!mHeaderOverwrite.contains(message)) {
        mHeaderOverwrite[message] = QVector<MessagePart::Ptr>();
    }
    mHeaderOverwrite[message].append(part);
}

QDateTime NodeHelper::dateHeader(KMime::Content *message) const
{
    const auto dateHeader = mailHeaderAsBase("date", message);
    if (dateHeader != nullptr) {
        return static_cast<const KMime::Headers::Date *>(dateHeader)->dateTime();
    }
    return QDateTime();
544
}
545
546
547
548
549
550
551
552
553
554
555
556

void NodeHelper::setOverrideCodec(KMime::Content *node, const QTextCodec *codec)
{
    if (!node) {
        return;
    }

    mOverrideCodecs[node] = codec;
}

const QTextCodec *NodeHelper::codec(KMime::Content *node)
{
Laurent Montel's avatar
Laurent Montel committed
557
    if (!node) {
558
559
560
        return mLocalCodec;
    }

Laurent Montel's avatar
Laurent Montel committed
561
    const QTextCodec *c = mOverrideCodecs.value(node, nullptr);
562
563
    if (!c) {
        // no override-codec set for this message, try the CT charset parameter:
564
565
566
567
568
569
570
571
        QByteArray charset = node->contentType()->charset();

        // utf-8 is a superset of us-ascii, so we don't loose anything, if we it insead
        // utf-8 is nowadays that widely, that it is a good guess to use it to fix issus with broken clients.
        if (charset.toLower() == "us-ascii") {
            charset = "utf-8";
        }
        c = codecForName(charset);
572
573
574
575
576
577
578
579
580
581
582
583
    }
    if (!c) {
        // no charset means us-ascii (RFC 2045), so using local encoding should
        // be okay
        c = mLocalCodec;
    }
    return c;
}

const QTextCodec *NodeHelper::codecForName(const QByteArray &_str)
{
    if (_str.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
584
        return nullptr;
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
    }
    QByteArray codec = _str.toLower();
    return KCharsets::charsets()->codecForName(QLatin1String(codec));
}

QString NodeHelper::fileName(const KMime::Content *node)
{
    QString name = const_cast<KMime::Content *>(node)->contentDisposition()->filename();
    if (name.isEmpty()) {
        name = const_cast<KMime::Content *>(node)->contentType()->name();
    }

    name = name.trimmed();
    return name;
}

Laurent Montel's avatar
Laurent Montel committed
601
// FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalBodyPartMemento replacement
Laurent Montel's avatar
Laurent Montel committed
602
Interface::BodyPartMemento *NodeHelper::bodyPartMemento(KMime::Content *node, const QByteArray &which) const
603
{
Laurent Montel's avatar
Laurent Montel committed
604
    const QMap<QString, QMap<QByteArray, Interface::BodyPartMemento *>>::const_iterator nit = mBodyPartMementoMap.find(persistentIndex(node));
605
    if (nit == mBodyPartMementoMap.end()) {
Laurent Montel's avatar
Laurent Montel committed
606
        return nullptr;
607
    }
Laurent Montel's avatar
Laurent Montel committed
608
    const QMap<QByteArray, Interface::BodyPartMemento *>::const_iterator it = nit->find(which.toLower());
Laurent Montel's avatar
Laurent Montel committed
609
    return it != nit->end() ? it.value() : nullptr;
610
611
}

Laurent Montel's avatar
Laurent Montel committed
612
// FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalSetBodyPartMemento replacement
Laurent Montel's avatar
Laurent Montel committed
613
void NodeHelper::setBodyPartMemento(KMime::Content *node, const QByteArray &which, Interface::BodyPartMemento *memento)
614
{
Laurent Montel's avatar
Laurent Montel committed
615
    QMap<QByteArray, Interface::BodyPartMemento *> &mementos = mBodyPartMementoMap[persistentIndex(node)];
616

Laurent Montel's avatar
Laurent Montel committed
617
    const QByteArray whichLower = which.toLower();
Laurent Montel's avatar
Laurent Montel committed
618
    const QMap<QByteArray, Interface::BodyPartMemento *>::iterator it = mementos.lowerBound(whichLower);
619

Laurent Montel's avatar
Laurent Montel committed
620
    if (it != mementos.end() && it.key() == whichLower) {
621
622
623
624
625
626
627
        delete it.value();
        if (memento) {
            it.value() = memento;
        } else {
            mementos.erase(it);
        }
    } else {
Laurent Montel's avatar
Laurent Montel committed
628
        mementos.insert(whichLower, memento);
629
630
631
632
633
    }
}

bool NodeHelper::isNodeDisplayedEmbedded(KMime::Content *node) const
{
634
    qCDebug(MIMETREEPARSER_LOG) << "IS NODE: " << mDisplayEmbeddedNodes.contains(node);
635
636
637
638
639
    return mDisplayEmbeddedNodes.contains(node);
}

void NodeHelper::setNodeDisplayedEmbedded(KMime::Content *node, bool displayedEmbedded)
{
640
    qCDebug(MIMETREEPARSER_LOG) << "SET NODE: " << node << displayedEmbedded;
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
    if (displayedEmbedded) {
        mDisplayEmbeddedNodes.insert(node);
    } else {
        mDisplayEmbeddedNodes.remove(node);
    }
}

bool NodeHelper::isNodeDisplayedHidden(KMime::Content *node) const
{
    return mDisplayHiddenNodes.contains(node);
}

void NodeHelper::setNodeDisplayedHidden(KMime::Content *node, bool displayedHidden)
{
    if (displayedHidden) {
        mDisplayHiddenNodes.insert(node);
    } else {
        mDisplayEmbeddedNodes.remove(node);
    }
}

/*!
  Creates a persistent index string that bridges the gap between the
  permanent nodes and the temporary ones.

  Used internally for robust indexing.
*/
QString NodeHelper::persistentIndex(const KMime::Content *node) const
{
    if (!node) {
        return QString();
    }

    QString indexStr = node->index().toString();
675
    if (indexStr.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
676
        QMapIterator<KMime::Message::Content *, QVector<KMime::Content *>> it(mExtraContents);
Laurent Montel's avatar
Laurent Montel committed
677
678
679
        while (it.hasNext()) {
            it.next();
            const auto &extraNodes = it.value();
Laurent Montel's avatar
Laurent Montel committed
680
            for (int i = 0; i < extraNodes.size(); ++i) {
681
                if (extraNodes[i] == node) {
Laurent Montel's avatar
Laurent Montel committed
682
                    indexStr = QStringLiteral("e%1").arg(i);
Laurent Montel's avatar
Laurent Montel committed
683
                    const QString parentIndex = persistentIndex(it.key());
684
                    if (!parentIndex.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
685
                        indexStr = QStringLiteral("%1:%2").arg(parentIndex, indexStr);
686
687
688
689
690
691
692
                    }
                    return indexStr;
                }
            }
        }
    } else {
        const KMime::Content *const topLevel = node->topLevel();
Laurent Montel's avatar
Laurent Montel committed
693
694
        // if the node is an extra node, prepend the index of the extra node to the url
        QMapIterator<KMime::Message::Content *, QVector<KMime::Content *>> it(mExtraContents);
Laurent Montel's avatar
Laurent Montel committed
695
696
        while (it.hasNext()) {
            it.next();
Laurent Montel's avatar
Laurent Montel committed
697
            const QVector<KMime::Content *> &extraNodes = extraContents(it.key());
698
699
700
            for (int i = 0; i < extraNodes.size(); ++i) {
                KMime::Content *const extraNode = extraNodes[i];
                if (topLevel == extraNode) {
Laurent Montel's avatar
Laurent Montel committed
701
                    indexStr.prepend(QStringLiteral("e%1:").arg(i));
Laurent Montel's avatar
Laurent Montel committed
702
                    const QString parentIndex = persistentIndex(it.key());
703
                    if (!parentIndex.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
704
                        indexStr = QStringLiteral("%1:%2").arg(parentIndex, indexStr);
705
706
                    }
                    return indexStr;
707
708
709
710
                }
            }
        }
    }
711

712
713
714
715
716
    return indexStr;
}

KMime::Content *NodeHelper::contentFromIndex(KMime::Content *node, const QString &persistentIndex) const
{
Laurent Montel's avatar
Laurent Montel committed
717
718
719
    if (!node) {
        return nullptr;
    }
720
721
    KMime::Content *c = node->topLevel();
    if (c) {
722
        const QStringList pathParts = persistentIndex.split(QLatin1Char(':'), Qt::SkipEmptyParts);
Laurent Montel's avatar
Minor    
Laurent Montel committed
723
724
        const int pathPartsSize(pathParts.size());
        for (int i = 0; i < pathPartsSize; ++i) {
725
726
            const QString &path = pathParts[i];
            if (path.startsWith(QLatin1Char('e'))) {
Laurent Montel's avatar
Laurent Montel committed
727
                const QVector<KMime::Content *> &extraParts = mExtraContents.value(c);
728
                const int idx = path.midRef(1, -1).toInt();
Laurent Montel's avatar
Laurent Montel committed
729
                c = (idx < extraParts.size()) ? extraParts[idx] : nullptr;
730
731
732
733
734
735
            } else {
                c = c->content(KMime::ContentIndex(path));
            }
            if (!c) {
                break;
            }
736
737
        }
    }
738
    return c;
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
}

QString NodeHelper::asHREF(const KMime::Content *node, const QString &place) const
{
    return QStringLiteral("attachment:%1?place=%2").arg(persistentIndex(node), place);
}

KMime::Content *NodeHelper::fromHREF(const KMime::Message::Ptr &mMessage, const QUrl &url) const
{
    if (url.isEmpty()) {
        return mMessage.data();
    }

    if (!url.isLocalFile()) {
        return contentFromIndex(mMessage.data(), url.adjusted(QUrl::StripTrailingSlash).path());
    } else {
        const QString path = url.toLocalFile();
756
757
758
        const QString extractedPath = extractAttachmentIndex(path);
        if (!extractedPath.isEmpty()) {
            return contentFromIndex(mMessage.data(), extractedPath);
759
760
761
762
763
        }
        return mMessage.data();
    }
}

764
765
766
767
768
769
QString NodeHelper::extractAttachmentIndex(const QString &path) const
{
    // extract from /<path>/qttestn28554.index.2.3:0:2/unnamed -> "2.3:0:2"
    // start of the index is something that is not a number followed by a dot: \D.
    // index is only made of numbers,"." and ":": ([0-9.:]+)
    // index is the last part of the folder name: /
Laurent Montel's avatar
Laurent Montel committed
770
    static const QRegularExpression re(QStringLiteral("\\D\\.([e0-9.:]+)/"));
Laurent Montel's avatar
Laurent Montel committed
771
772
773
774
    QRegularExpressionMatch rmatch;
    path.lastIndexOf(re, -1, &rmatch);
    if (rmatch.hasMatch()) {
        return rmatch.captured(1);
775
776
777
778
    }
    return QString();
}

779
780
781
QString NodeHelper::fixEncoding(const QString &encoding)
{
    QString returnEncoding = encoding;
Yuri Chornoivan's avatar
Yuri Chornoivan committed
782
    // According to https://www.iana.org/assignments/character-sets, uppercase is
783
    // preferred in MIME headers
784
    const QString returnEncodingToUpper = returnEncoding.toUpper();
Laurent Montel's avatar
Laurent Montel committed
785
    if (returnEncodingToUpper.contains(QLatin1String("ISO "))) {
786
        returnEncoding = returnEncodingToUpper;
787
788
789
790
791
792
793
794
        returnEncoding.replace(QLatin1String("ISO "), QStringLiteral("ISO-"));
    }
    return returnEncoding;
}

//-----------------------------------------------------------------------------
QString NodeHelper::encodingForName(const QString &descriptiveName)
{
Laurent Montel's avatar
Laurent Montel committed
795
    const QString encoding = KCharsets::charsets()->encodingForName(descriptiveName);
796
797
798
799
800
801
802
803
804
    return NodeHelper::fixEncoding(encoding);
}

QStringList NodeHelper::supportedEncodings(bool usAscii)
{
    QStringList encodingNames = KCharsets::charsets()->availableEncodingNames();
    QStringList encodings;
    QMap<QString, bool> mimeNames;
    QStringList::ConstIterator constEnd(encodingNames.constEnd());
Laurent Montel's avatar
Laurent Montel committed
805
    for (QStringList::ConstIterator it = encodingNames.constBegin(); it != constEnd; ++it) {
806
        QTextCodec *codec = KCharsets::charsets()->codecForName(*it);
Laurent Montel's avatar
Laurent Montel committed
807
        const QString mimeName = (codec) ? QString::fromLatin1(codec->name()).toLower() : (*it);
808
809
810
811
812
813
814
815
816
817
818
819
        if (!mimeNames.contains(mimeName)) {
            encodings.append(KCharsets::charsets()->descriptionForEncoding(*it));
            mimeNames.insert(mimeName, true);
        }
    }
    encodings.sort();
    if (usAscii) {
        encodings.prepend(KCharsets::charsets()->descriptionForEncoding(QStringLiteral("us-ascii")));
    }
    return encodings;
}

820
QString NodeHelper::fromAsString(KMime::Content *node) const
821
{
822
    if (auto topLevel = dynamic_cast<KMime::Message *>(node->topLevel())) {
823
        return topLevel->from()->asUnicodeString();
824
    } else {
Laurent Montel's avatar
Laurent Montel committed
825
826
827
        auto realNode = std::find_if(mExtraContents.cbegin(), mExtraContents.cend(), [node](const QVector<KMime::Content *> &nodes) {
            return nodes.contains(node);
        });
828
829
830
        if (realNode != mExtraContents.cend()) {
            return fromAsString(realNode.key());
        }
831
    }
832

833
834
835
836
837
    return QString();
}

void NodeHelper::attachExtraContent(KMime::Content *topLevelNode, KMime::Content *content)
{
838
    qCDebug(MIMETREEPARSER_LOG) << "mExtraContents added for" << topLevelNode << " extra content: " << content;
839
840
841
    mExtraContents[topLevelNode].append(content);
}

Sandro Knauß's avatar
Sandro Knauß committed
842
843
844
845
846
847
void NodeHelper::cleanExtraContent(KMime::Content *topLevelNode)
{
    qCDebug(MIMETREEPARSER_LOG) << "remove all extraContents for" << topLevelNode;
    mExtraContents[topLevelNode].clear();
}

Laurent Montel's avatar
Laurent Montel committed
848
QVector<KMime::Content *> NodeHelper::extraContents(KMime::Content *topLevelnode) const
849
850
851
852
853
854
855
856
857
858
{
    return mExtraContents.value(topLevelnode);
}

void NodeHelper::mergeExtraNodes(KMime::Content *node)
{
    if (!node) {
        return;
    }

Laurent Montel's avatar
Laurent Montel committed
859
    const QVector<KMime::Content *> extraNodes = extraContents(node);
860
    for (KMime::Content *extra : extraNodes) {
861
        if (node->bodyIsMessage()) {
Laurent Montel's avatar
Laurent Montel committed
862
            qCWarning(MIMETREEPARSER_LOG) << "Asked to attach extra content to a kmime::message, this does not make sense. Attaching to:" << node
Laurent Montel's avatar
Laurent Montel committed
863
864
                                          << node->encodedContent() << "\n====== with =======\n"
                                          << extra << extra->encodedContent();
865
866
            continue;
        }
Laurent Montel's avatar
Laurent Montel committed
867
        auto c = new KMime::Content(node);
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
        c->setContent(extra->encodedContent());
        c->parse();
        node->addContent(c);
    }

    Q_FOREACH (KMime::Content *child, node->contents()) {
        mergeExtraNodes(child);
    }
}

void NodeHelper::cleanFromExtraNodes(KMime::Content *node)
{
    if (!node) {
        return;
    }
Laurent Montel's avatar
Laurent Montel committed
883
    const QVector<KMime::Content *> extraNodes = extraContents(node);
Laurent Montel's avatar
Laurent Montel committed
884
    for (KMime::Content *extra : extraNodes) {
885
        QByteArray s = extra->encodedContent();
Laurent Montel's avatar
Laurent Montel committed
886
887
        const auto children = node->contents();
        for (KMime::Content *c : children) {
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
            if (c->encodedContent() == s) {
                node->removeContent(c);
            }
        }
    }
    Q_FOREACH (KMime::Content *child, node->contents()) {
        cleanFromExtraNodes(child);
    }
}

KMime::Message *NodeHelper::messageWithExtraContent(KMime::Content *topLevelNode)
{
    /*The merge is done in several steps:
      1) merge the extra nodes into topLevelNode
      2) copy the modified (merged) node tree into a new node tree
      3) restore the original node tree in topLevelNode by removing the extra nodes from it

      The reason is that extra nodes are assigned by pointer value to the nodes in the original tree.
    */
    if (!topLevelNode) {
Laurent Montel's avatar
Laurent Montel committed
908
        return nullptr;
909
910
911
912
    }

    mergeExtraNodes(topLevelNode);

Laurent Montel's avatar
Laurent Montel committed
913
    auto m = new KMime::Message;
914
915
916
917
    m->setContent(topLevelNode->encodedContent());
    m->parse();

    cleanFromExtraNodes(topLevelNode);
Laurent Montel's avatar
Laurent Montel committed
918
919
    //   qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITH EXTRA: " << m->encodedContent();
    //   qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITHOUT EXTRA: " << topLevelNode->encodedContent();
920
921
922
923
924
925

    return m;
}

KMime::Content *NodeHelper::decryptedNodeForContent(KMime::Content *content) const
{
Laurent Montel's avatar
Laurent Montel committed
926
    const QVector<KMime::Content *> xc = extraContents(content);
927
928
929
930
    if (!xc.empty()) {
        if (xc.size() == 1) {
            return xc.front();
        } else {
931
            qCWarning(MIMETREEPARSER_LOG) << "WTF, encrypted node has multiple extra contents?";
932
933
        }
    }
Laurent Montel's avatar
Laurent Montel committed
934
    return nullptr;
935
936
}

Laurent Montel's avatar
Laurent Montel committed
937
bool NodeHelper::unencryptedMessage_helper(KMime::Content *node, QByteArray &resultingData, bool addHeaders, int recursionLevel)
938
939
940
941
{
    bool returnValue = false;
    if (node) {
        KMime::Content *curNode = node;
Laurent Montel's avatar
Laurent Montel committed
942
        KMime::Content *decryptedNode = nullptr;
943
944
945
        const QByteArray type = node->contentType(false) ? QByteArray(node->contentType(false)->mediaType()).toLower() : "text";
        const QByteArray subType = node->contentType(false) ? node->contentType(false)->subType().toLower() : "plain";
        const bool isMultipart = node->contentType(false) && node->contentType(false)->isMultipart();
946
947
        bool isSignature = false;

948
        qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") Looking at" << type << "/" << subType;
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970

        if (isMultipart) {
            if (subType == "signed") {
                isSignature = true;
            } else if (subType == "encrypted") {
                decryptedNode = decryptedNodeForContent(curNode);
            }
        } else if (type == "application") {
            if (subType == "octet-stream") {
                decryptedNode = decryptedNodeForContent(curNode);
            } else if (subType == "pkcs7-signature") {
                isSignature = true;
            } else if (subType == "pkcs7-mime") {
                // note: subtype pkcs7-mime can also be signed
                //       and we do NOT want to remove the signature!
                if (encryptionState(curNode) != KMMsgNotEncrypted) {
                    decryptedNode = decryptedNodeForContent(curNode);
                }
            }
        }

        if (decryptedNode) {
971
            qCDebug(MIMETREEPARSER_LOG) << "Current node has an associated decrypted node, adding a modified header "
Laurent Montel's avatar
Laurent Montel committed
972
                                           "and then processing the children.";
973
974
975
976
977

            Q_ASSERT(addHeaders);
            KMime::Content headers;
            headers.setHead(curNode->head());
            headers.parse();
Laurent Montel's avatar
Laurent Montel committed
978
979
            if (auto ct = decryptedNode->contentType(false)) {
                headers.contentType()->from7BitString(ct->as7BitString(false));
980
981
982
            } else {
                headers.removeHeader<KMime::Headers::ContentType>();
            }
Laurent Montel's avatar
Laurent Montel committed
983
984
            if (auto ct = decryptedNode->contentTransferEncoding(false)) {
                headers.contentTransferEncoding()->from7BitString(ct->as7BitString(false));
985
986
987
            } else {
                headers.removeHeader<KMime::Headers::ContentTransferEncoding>();
            }
Laurent Montel's avatar
Laurent Montel committed
988
989
            if (auto cd = decryptedNode->contentDisposition(false)) {
                headers.contentDisposition()->from7BitString(cd->as7BitString(false));
990
991
992
            } else {
                headers.removeHeader<KMime::Headers::ContentDisposition>();
            }
Laurent Montel's avatar
Laurent Montel committed
993
994
            if (auto cd = decryptedNode->contentDescription(false)) {
                headers.contentDescription()->from7BitString(cd->as7BitString(false));
995
996
997
998
999
1000
1001
1002
1003
            } else {
                headers.removeHeader<KMime::Headers::ContentDescription>();
            }
            headers.assemble();

            resultingData += headers.head() + '\n';
            unencryptedMessage_helper(decryptedNode, resultingData, false, recursionLevel + 1);

            returnValue = true;
Laurent Montel's avatar
Laurent Montel committed
1004
        } else if (isSignature) {
1005
            qCDebug(MIMETREEPARSER_LOG) << "Current node is a signature, adding it as-is.";
1006
1007
1008
1009
1010
1011
1012
            // We can't change the nodes under the signature, as that would invalidate it. Add the signature
            // and its child as-is
            if (addHeaders) {
                resultingData += curNode->head() + '\n';
            }
            resultingData += curNode->encodedBody();
            returnValue = false;
Laurent Montel's avatar
Laurent Montel committed
1013
        } else if (isMultipart) {
1014
            qCDebug(MIMETREEPARSER_LOG) << "Current node is a multipart node, adding its header and then processing all children.";
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
            // Normal multipart node, add the header and all of its children
            bool somethingChanged = false;
            if (addHeaders) {
                resultingData += curNode->head() + '\n';
            }
            const QByteArray boundary = curNode->contentType()->boundary();
            foreach (KMime::Content *child, curNode->contents()) {
                resultingData += "\n--" + boundary + '\n';
                const bool changed = unencryptedMessage_helper(child, resultingData, true, recursionLevel + 1);
                if (changed) {
                    somethingChanged = true;
                }
            }
            resultingData += "\n--" + boundary + "--\n\n";
            returnValue = somethingChanged;
Laurent Montel's avatar
Laurent Montel committed
1030
        } else if (curNode->bodyIsMessage()) {
1031
            qCDebug(MIMETREEPARSER_LOG) << "Current node is a message, adding the header and then processing the child.";
1032
1033
1034
1035
1036
            if (addHeaders) {
                resultingData += curNode->head() + '\n';
            }

            returnValue = unencryptedMessage_helper(curNode->bodyAsMessage().data(), resultingData, true, recursionLevel + 1);
Laurent Montel's avatar
Laurent Montel committed
1037
        } else {
1038
            qCDebug(MIMETREEPARSER_LOG) << "Current node is an ordinary leaf node, adding it as-is.";
1039
1040
1041
1042
1043
1044
1045
1046
            if (addHeaders) {
                resultingData += curNode->head() + '\n';
            }
            resultingData += curNode->body();
            returnValue = false;
        }
    }

1047
    qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") done.";
1048
1049
1050
1051
1052
1053
1054
1055
1056
    return returnValue;
}

KMime::Message::Ptr NodeHelper::unencryptedMessage(const KMime::Message::Ptr &originalMessage)
{
    QByteArray resultingData;
    const bool messageChanged = unencryptedMessage_helper(originalMessage.data(), resultingData, true);
    if (messageChanged) {
#if 0
1057
        qCDebug(MIMETREEPARSER_LOG) << "Resulting data is:" << resultingData;
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
        QFile bla("stripped.mbox");
        bla.open(QIODevice::WriteOnly);
        bla.write(resultingData);
        bla.close();
#endif
        KMime::Message::Ptr newMessage(new KMime::Message);
        newMessage->setContent(resultingData);
        newMessage->parse();
        return newMessage;
    } else {
        return KMime::Message::Ptr();
    }
}

QVector<KMime::Content *> NodeHelper::attachmentsOfExtraContents() const
{
    QVector<KMime::Content *> result;
Laurent Montel's avatar
Laurent Montel committed
1075
    for (auto it = mExtraContents.begin(), end = mExtraContents.end(); it != end; ++it) {
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
        foreach (auto content, it.value()) {
            if (KMime::isAttachment(content)) {
                result.push_back(content);
            } else {
                result += content->attachments();
            }
        }
    }
    return result;
}
}