messagepart.cpp 64.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
   Copyright (c) 2015 Sandro Knauß <sknauss@kde.org>

   This library is free software; you can redistribute it and/or modify it
   under the terms of the GNU Library General Public License as published by
   the Free Software Foundation; either version 2 of the License, or (at your
   option) any later version.

   This library is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
   License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to the
   Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
   02110-1301, USA.
*/

#include "messagepart.h"
#include "messageviewer_debug.h"
#include "objecttreeparser.h"
23
#include "converthtmltoplaintext.h"
24
#include "csshelperbase.h"
25
#include "cryptohelper.h"
26
#include "utils/iconnamecache.h"
27
28
29
30

#include <MessageCore/StringUtil>

#include <libkleo/importjob.h>
31
#include <libkleo/cryptobackendfactory.h>
32
33

#include <interfaces/htmlwriter.h>
Sandro Knauß's avatar
Sandro Knauß committed
34
#include <job/kleojobexecutor.h>
35
36
37
38
39
40
#include <kmime/kmime_content.h>
#include <gpgme++/key.h>
#include <gpgme.h>

#include <QTextCodec>
#include <QApplication>
41
42
43
#include <QWebPage>
#include <QWebElement>
#include <QWebFrame>
44
45

#include <KLocalizedString>
46
#include <KEmailAddress>
47
48
49

using namespace MessageViewer;

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/** Checks whether @p str contains external references. To be precise,
    we only check whether @p str contains 'xxx="http[s]:' where xxx is
    not href. Obfuscated external references are ignored on purpose.
*/

bool containsExternalReferences(const QString &str, const QString &extraHead)
{
    const bool hasBaseInHeader = extraHead.contains(QStringLiteral("<base href=\""), Qt::CaseInsensitive);
    if (hasBaseInHeader && (str.contains(QStringLiteral("href=\"/"), Qt::CaseInsensitive) ||
                            str.contains(QStringLiteral("<img src=\"/"), Qt::CaseInsensitive))) {
        return true;
    }
    /*
    //Laurent: workaround for local ref cid
    if(str.contains(QStringLiteral("<img src=\"cid:"),Qt::CaseInsensitive)) {
    return true;
    }
    */
    int httpPos = str.indexOf(QLatin1String("\"http:"), Qt::CaseInsensitive);
    int httpsPos = str.indexOf(QLatin1String("\"https:"), Qt::CaseInsensitive);

    while (httpPos >= 0 || httpsPos >= 0) {
        // pos = index of next occurrence of "http: or "https: whichever comes first
        int pos = (httpPos < httpsPos)
                  ? ((httpPos >= 0) ? httpPos : httpsPos)
                  : ((httpsPos >= 0) ? httpsPos : httpPos);
        // look backwards for "href"
        if (pos > 5) {
            int hrefPos = str.lastIndexOf(QLatin1String("href"), pos - 5, Qt::CaseInsensitive);
            // if no 'href' is found or the distance between 'href' and '"http[s]:'
            // is larger than 7 (7 is the distance in 'href = "http[s]:') then
            // we assume that we have found an external reference
            if ((hrefPos == -1) || (pos - hrefPos > 7)) {

                // HTML messages created by KMail itself for now contain the following:
                // <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
                // Make sure not to show an external references warning for this string
                int dtdPos = str.indexOf(QLatin1String("http://www.w3.org/TR/html4/loose.dtd"), pos + 1);
                if (dtdPos != (pos + 1)) {
                    return true;
                }
            }
        }
        // find next occurrence of "http: or "https:
        if (pos == httpPos) {
            httpPos = str.indexOf(QLatin1String("\"http:"), httpPos + 6, Qt::CaseInsensitive);
        } else {
            httpsPos = str.indexOf(QLatin1String("\"https:"), httpsPos + 7, Qt::CaseInsensitive);
        }
    }
    return false;
}

103
104
105
106
107
QString HTMLBlock::dir() const
{
    return QApplication::isRightToLeft() ? QStringLiteral("rtl") : QStringLiteral("ltr");
}

108
109
110
111
//--------CryptoBlock-------------------
CryptoBlock::CryptoBlock(ObjectTreeParser *otp,
                         PartMetaData *block,
                         const Kleo::CryptoBackend::Protocol *cryptoProto,
112
                         ObjectTreeSourceIf *source,
113
114
115
116
117
118
                         const QString &fromAddress,
                         KMime::Content *node)
    : HTMLBlock()
    , mOtp(otp)
    , mMetaData(block)
    , mCryptoProto(cryptoProto)
119
    , mSource(source)
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
    , mFromAddress(fromAddress)
    , mNode(node)
{
    internalEnter();
}

CryptoBlock::~CryptoBlock()
{
    internalExit();
}

void CryptoBlock::internalEnter()
{
    MessageViewer::HtmlWriter *writer = mOtp->htmlWriter();
    if (writer && !entered) {
        entered = true;
136
137
138
139
140
141
        if (mMetaData->isEncapsulatedRfc822Message) {
            mInteralBlocks.append(HTMLBlock::Ptr(new EncapsulatedRFC822Block(writer, mOtp->nodeHelper(), mNode)));
        }
        if (mMetaData->isEncrypted) {
            mInteralBlocks.append(HTMLBlock::Ptr(new EncryptedBlock(writer, *mMetaData)));
        }
142
143
144
        if (mMetaData->isSigned) {
            mInteralBlocks.append(HTMLBlock::Ptr(new SignedBlock(writer, *mMetaData, mOtp->cryptoProtocol(), mSource, mFromAddress, false)));
        }
145
146
147
148
149
150
151
152
    }
}

void CryptoBlock::internalExit()
{
    if (!entered) {
        return;
    }
153

154
155
156
157
158
159
    while (!mInteralBlocks.isEmpty()) {
        mInteralBlocks.removeLast();
    }
    entered = false;
}

Laurent Montel's avatar
Laurent Montel committed
160
EncapsulatedRFC822Block::EncapsulatedRFC822Block(MessageViewer::HtmlWriter *writer, MessageViewer::NodeHelper *nodeHelper, KMime::Content *node)
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
    : mWriter(writer)
    , mNodeHelper(nodeHelper)
    , mNode(node)
{
    internalEnter();
}

EncapsulatedRFC822Block::~EncapsulatedRFC822Block()
{
    internalExit();
}

void EncapsulatedRFC822Block::internalEnter()
{
    if (mWriter && !entered) {
176
        QString text;
177
178
        if (mNode) {
            const QString href = mNodeHelper->asHREF(mNode, QStringLiteral("body"));
179
            text = QStringLiteral("<a href=\"%1\">%2</a>").arg(href, i18n("Encapsulated message"));
180
        } else {
181
            text = i18n("Encapsulated message");
182
        }
183
        mWriter->queue(QStringLiteral("<table cellspacing=\"1\" cellpadding=\"1\" class=\"rfc822\">") +
184
                       QStringLiteral("<tr class=\"rfc822H\"><td dir=\"%1\">").arg(dir()) +
185
186
                       text +
                       QStringLiteral("</td></tr><tr class=\"rfc822B\"><td>"));
187
188
189
190
191
192
193
194
195
196
197

        entered = true;
    }
}

void EncapsulatedRFC822Block::internalExit()
{
    if (!entered) {
        return;
    }

198
    mWriter->queue(QStringLiteral("</td></tr>"
Laurent Montel's avatar
Laurent Montel committed
199
200
                                  "<tr class=\"rfc822H\"><td dir=\"%1\">%2</td></tr>"
                                  "</table>").arg(dir(), i18n("End of encapsulated message")));
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
    entered = false;
}

EncryptedBlock::EncryptedBlock(MessageViewer::HtmlWriter *writer, const PartMetaData &block)
    : mWriter(writer)
    , mBlock(block)
{
    internalEnter();
}

EncryptedBlock::~EncryptedBlock()
{
    internalExit();
}

void EncryptedBlock::internalEnter()
{
    if (mWriter && !entered) {
        entered = true;
Sandro Knauß's avatar
Sandro Knauß committed
220
        QString text;
221
        if (mBlock.inProgress) {
Sandro Knauß's avatar
Sandro Knauß committed
222
            text = i18n("Please wait while the message is being decrypted...");
223
        } else if (mBlock.isDecryptable) {
Sandro Knauß's avatar
Sandro Knauß committed
224
            text = i18n("Encrypted message");
225
        } else {
Sandro Knauß's avatar
Sandro Knauß committed
226
            text = i18n("Encrypted message (decryption not possible)");
227
            if (!mBlock.errorText.isEmpty()) {
Sandro Knauß's avatar
Sandro Knauß committed
228
                text += QStringLiteral("<br />") + i18n("Reason: %1", mBlock.errorText);
229
230
            }
        }
Sandro Knauß's avatar
Sandro Knauß committed
231
        mWriter->queue(QStringLiteral("<table cellspacing=\"1\" cellpadding=\"1\" class=\"encr\">") +
232
                       QStringLiteral("<tr class=\"encrH\"><td dir=\"%1\">").arg(dir()) +
Sandro Knauß's avatar
Sandro Knauß committed
233
234
                       text +
                       QStringLiteral("</td></tr><tr class=\"encrB\"><td>"));
235
236
237
238
239
240
241
242
    }
}

void EncryptedBlock::internalExit()
{
    if (!entered) {
        return;
    }
Sandro Knauß's avatar
Sandro Knauß committed
243
    mWriter->queue(QStringLiteral("</td></tr>"
Laurent Montel's avatar
Laurent Montel committed
244
245
                                  "<tr class=\"encrH\"><td dir=\"%1\">%2</td></tr>"
                                  "</table>").arg(dir(), i18n("End of encrypted message")));
246
247
248
    entered = false;
}

249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
SignedBlock::SignedBlock(MessageViewer::HtmlWriter *writer, const PartMetaData &block,
                         const Kleo::CryptoBackend::Protocol *cryptoProto,
                         ObjectTreeSourceIf *source,
                         QString fromAddress, bool printing)
    : HTMLBlock()
    , mBlock(block)
    , mWriter(writer)
    , mCryptoProto(cryptoProto)
    , mSource(source)
    , mFromAddress(fromAddress)
    , mPrinting(printing)
{
    internalEnter();
}

SignedBlock::~SignedBlock()
{
    internalExit();
}

static const int SIG_FRAME_COL_UNDEF = 99;
#define SIG_FRAME_COL_RED    -1
#define SIG_FRAME_COL_YELLOW  0
#define SIG_FRAME_COL_GREEN   1
QString sigStatusToString(const Kleo::CryptoBackend::Protocol *cryptProto,
Laurent Montel's avatar
Laurent Montel committed
274
275
276
277
                          int status_code,
                          GpgME::Signature::Summary summary,
                          int &frameColor,
                          bool &showKeyInfos)
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
{
    // note: At the moment frameColor and showKeyInfos are
    //       used for CMS only but not for PGP signatures
    // pending(khz): Implement usage of these for PGP sigs as well.
    showKeyInfos = true;
    QString result;
    if (cryptProto) {
        if (cryptProto == Kleo::CryptoBackendFactory::instance()->openpgp()) {
            // process enum according to it's definition to be read in
            // GNU Privacy Guard CVS repository /gpgme/gpgme/gpgme.h
            switch (status_code) {
            case 0: // GPGME_SIG_STAT_NONE
                result = i18n("Error: Signature not verified");
                break;
            case 1: // GPGME_SIG_STAT_GOOD
                result = i18n("Good signature");
                break;
            case 2: // GPGME_SIG_STAT_BAD
                result = i18n("<b>Bad</b> signature");
                break;
            case 3: // GPGME_SIG_STAT_NOKEY
                result = i18n("No public key to verify the signature");
                break;
            case 4: // GPGME_SIG_STAT_NOSIG
                result = i18n("No signature found");
                break;
            case 5: // GPGME_SIG_STAT_ERROR
                result = i18n("Error verifying the signature");
                break;
            case 6: // GPGME_SIG_STAT_DIFF
                result = i18n("Different results for signatures");
                break;
            /* PENDING(khz) Verify exact meaning of the following values:
            case 7: // GPGME_SIG_STAT_GOOD_EXP
            return i18n("Signature certificate is expired");
            break;
            case 8: // GPGME_SIG_STAT_GOOD_EXPKEY
            return i18n("One of the certificate's keys is expired");
            break;
            */
            default:
                result.clear();   // do *not* return a default text here !
                break;
            }
        } else if (cryptProto == Kleo::CryptoBackendFactory::instance()->smime()) {
            // process status bits according to SigStatus_...
            // definitions in kdenetwork/libkdenetwork/cryptplug.h

            if (summary == GpgME::Signature::None) {
                result = i18n("No status information available.");
                frameColor = SIG_FRAME_COL_YELLOW;
                showKeyInfos = false;
                return result;
            }

            if (summary & GpgME::Signature::Valid) {
                result = i18n("Good signature.");
                // Note:
                // Here we are work differently than KMail did before!
                //
                // The GOOD case ( == sig matching and the complete
                // certificate chain was verified and is valid today )
                // by definition does *not* show any key
                // information but just states that things are OK.
                //           (khz, according to LinuxTag 2002 meeting)
                frameColor = SIG_FRAME_COL_GREEN;
                showKeyInfos = false;
                return result;
            }

            // we are still there?  OK, let's test the different cases:

            // we assume green, test for yellow or red (in this order!)
            frameColor = SIG_FRAME_COL_GREEN;
            QString result2;
            if (summary & GpgME::Signature::KeyExpired) {
                // still is green!
                result2 += i18n("One key has expired.");
            }
            if (summary & GpgME::Signature::SigExpired) {
                // and still is green!
                result2 += i18n("The signature has expired.");
            }

            // test for yellow:
            if (summary & GpgME::Signature::KeyMissing) {
                result2 += i18n("Unable to verify: key missing.");
                // if the signature certificate is missing
                // we cannot show information on it
                showKeyInfos = false;
                frameColor = SIG_FRAME_COL_YELLOW;
            }
            if (summary & GpgME::Signature::CrlMissing) {
                result2 += i18n("CRL not available.");
                frameColor = SIG_FRAME_COL_YELLOW;
            }
            if (summary & GpgME::Signature::CrlTooOld) {
                result2 += i18n("Available CRL is too old.");
                frameColor = SIG_FRAME_COL_YELLOW;
            }
            if (summary & GpgME::Signature::BadPolicy) {
                result2 += i18n("A policy was not met.");
                frameColor = SIG_FRAME_COL_YELLOW;
            }
            if (summary & GpgME::Signature::SysError) {
                result2 += i18n("A system error occurred.");
                // if a system error occurred
                // we cannot trust any information
                // that was given back by the plug-in
                showKeyInfos = false;
                frameColor = SIG_FRAME_COL_YELLOW;
            }

            // test for red:
            if (summary & GpgME::Signature::KeyRevoked) {
                // this is red!
                result2 += i18n("One key has been revoked.");
                frameColor = SIG_FRAME_COL_RED;
            }
            if (summary & GpgME::Signature::Red) {
                if (result2.isEmpty())
                    // Note:
                    // Here we are work differently than KMail did before!
                    //
                    // The BAD case ( == sig *not* matching )
                    // by definition does *not* show any key
                    // information but just states that things are BAD.
                    //
                    // The reason for this: In this case ALL information
                    // might be falsificated, we can NOT trust the data
                    // in the body NOT the signature - so we don't show
                    // any key/signature information at all!
                    //         (khz, according to LinuxTag 2002 meeting)
                {
                    showKeyInfos = false;
                }
                frameColor = SIG_FRAME_COL_RED;
            } else {
                result.clear();
            }

            if (SIG_FRAME_COL_GREEN == frameColor) {
                result = i18n("Good signature.");
            } else if (SIG_FRAME_COL_RED == frameColor) {
                result = i18n("<b>Bad</b> signature.");
            } else {
                result.clear();
            }

            if (!result2.isEmpty()) {
                if (!result.isEmpty()) {
                    result.append(QLatin1String("<br />"));
                }
                result.append(result2);
            }
        }
        /*
        // add i18n support for 3rd party plug-ins here:
        else if ( cryptPlug->libName().contains( "yetanotherpluginname", Qt::CaseInsensitive )) {

        }
        */
    }
    return result;
}

QString SignedBlock::simpleHeader()
{
    QString text;
    if (mClass == QStringLiteral("signErr")) {
        text = i18n("Invalid signature.");
    } else if (mClass == QStringLiteral("signOkKeyBad")
               || mClass == QStringLiteral("signWarn")) {
Laurent Montel's avatar
Laurent Montel committed
451
        text = i18n("Not enough information to check signature validity.");
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
    } else if (mClass == QStringLiteral("signOkKeyOk")) {

        QString addr;
        if (!mBlock.signerMailAddresses.isEmpty()) {
            addr = mBlock.signerMailAddresses.first();
        }

        QString name = addr;
        if (name.isEmpty()) {
            name = mBlock.signer;
        }

        if (addr.isEmpty()) {
            text = i18n("Signature is valid.");
        } else {
Laurent Montel's avatar
Laurent Montel committed
467
            text = i18n("Signed by <a href=\"mailto:%1\">%2</a>.", addr, name);
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
        }

    } else {
        // should not happen
        text = i18n("Unknown signature state");
    }

    QString html;
    html += QStringLiteral("<table cellspacing=\"0\" cellpadding=\"0\" width=\"100%\"><tr>");
    html += QStringLiteral("<td>%1</td>").arg(text);
    if (!mPrinting) {
        html += QStringLiteral("<td align=\"right\">");
        html += QStringLiteral("<a href=\"kmail:showSignatureDetails\">%1</a>").arg(i18n("Show Details"));
        html += QStringLiteral("</td>");
    }
    html += QStringLiteral("</tr></table>");
    return html;
}

static QString beginVerboseSigstatHeader()
{
    return QStringLiteral("<table cellspacing=\"0\" cellpadding=\"0\" width=\"100%\"><tr><td rowspan=\"2\">");
}

static QString makeShowAuditLogLink(const GpgME::Error &err, const QString &auditLog)
{
    // more or less the same as
    // kleopatra/utils/auditlog.cpp:formatLink(), so any bug fixed here
    // equally applies there:
    if (const unsigned int code = err.code()) {
        if (code == GPG_ERR_NOT_IMPLEMENTED) {
            qCDebug(MESSAGEVIEWER_LOG) << "not showing link (not implemented)";
            return QString();
        } else if (code == GPG_ERR_NO_DATA) {
            qCDebug(MESSAGEVIEWER_LOG) << "not showing link (not available)";
            return i18n("No Audit Log available");
        } else {
            return i18n("Error Retrieving Audit Log: %1", QString::fromLocal8Bit(err.asString()));
        }
    }

    if (!auditLog.isEmpty()) {
        QUrl url;
        url.setScheme(QStringLiteral("kmail"));
        url.setPath(QStringLiteral("showAuditLog"));
        QUrlQuery urlquery(url);
        urlquery.addQueryItem(QStringLiteral("log"), auditLog);
        url.setQuery(urlquery);
        return QStringLiteral("<a href=\"%1\">%2</a>").arg(url.toDisplayString(), i18nc("The Audit Log is a detailed error log from the gnupg backend", "Show Audit Log"));
    }

    return QString();
}

static QString endVerboseSigstatHeader(const PartMetaData &pmd)
{
    QString html;
    html += QStringLiteral("</td><td align=\"right\" valign=\"top\" nowrap=\"nowrap\">");
    html += QStringLiteral("<a href=\"kmail:hideSignatureDetails\">");
    html += i18n("Hide Details");
    html += QStringLiteral("</a></td></tr>");
    html += QStringLiteral("<tr><td align=\"right\" valign=\"bottom\" nowrap=\"nowrap\">");
    html += makeShowAuditLogLink(pmd.auditLogError, pmd.auditLog);
    html += QStringLiteral("</td></tr></table>");
    return html;
}

void SignedBlock::internalEnter()
{
    if (mWriter && !entered) {
        entered = true;
        const bool isSMIME = mCryptoProto && (mCryptoProto == Kleo::CryptoBackendFactory::instance()->smime());
        QString signer = mBlock.signer;

        QString htmlStr, simpleHtmlStr;

        if (mBlock.inProgress) {
            mClass = QStringLiteral("signInProgress");
            mWriter->queue(QStringLiteral("<table cellspacing=\"1\" cellpadding=\"1\" class=\"signInProgress\">") +
Laurent Montel's avatar
Laurent Montel committed
547
                           QStringLiteral("<tr class=\"signInProgressH\"><td dir=\"%1\">").arg(dir()) +
548
549
550
                           i18n("Please wait while the signature is being verified...") +
                           QStringLiteral("</td></tr><tr class=\"signInProgressB\"><td>"));
            return;
Sandro Knauß's avatar
Sandro Knauß committed
551
        } else {
552
553
554
555
556
557
558
559
560
            const QStringList &blockAddrs(mBlock.signerMailAddresses);
            // note: At the moment frameColor and showKeyInfos are
            //       used for CMS only but not for PGP signatures
            // pending(khz): Implement usage of these for PGP sigs as well.
            int frameColor = SIG_FRAME_COL_UNDEF;
            bool showKeyInfos;
            bool onlyShowKeyURL = false;
            bool cannotCheckSignature = true;
            QString statusStr = sigStatusToString(mCryptoProto,
Laurent Montel's avatar
Laurent Montel committed
561
562
563
564
                                                  mBlock.status_code,
                                                  mBlock.sigSummary,
                                                  frameColor,
                                                  showKeyInfos);
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
            // if needed fallback to english status text
            // that was reported by the plugin
            if (statusStr.isEmpty()) {
                statusStr = mBlock.status;
            }
            if (mBlock.technicalProblem) {
                frameColor = SIG_FRAME_COL_YELLOW;
            }

            switch (frameColor) {
            case SIG_FRAME_COL_RED:
                cannotCheckSignature = false;
                break;
            case SIG_FRAME_COL_YELLOW:
                cannotCheckSignature = true;
                break;
            case SIG_FRAME_COL_GREEN:
                cannotCheckSignature = false;
                break;
            }

            // compose the string for displaying the key ID
            // either as URL or not linked (for unknown crypto)
            // note: Once we can start PGP key manager programs
            //       from within KMail we could change this and
            //       always show the URL.    (khz, 2002/06/27)
            QString startKeyHREF;
            QString keyWithWithoutURL;
            if (mCryptoProto) {
                startKeyHREF =
                    QStringLiteral("<a href=\"kmail:showCertificate#%1 ### %2 ### %3\">")
                    .arg(mCryptoProto->displayName(),
Laurent Montel's avatar
Laurent Montel committed
597
598
                         mCryptoProto->name(),
                         QString::fromLatin1(mBlock.keyId));
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
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
675

                keyWithWithoutURL =
                    QStringLiteral("%1%2</a>").arg(startKeyHREF, QString::fromLatin1(QByteArray(QByteArray("0x") + mBlock.keyId)));
            } else {
                keyWithWithoutURL = QStringLiteral("0x") + QString::fromUtf8(mBlock.keyId);
            }

            // temporary hack: always show key information!
            showKeyInfos = true;

            if (isSMIME && (SIG_FRAME_COL_UNDEF != frameColor)) {

                // new frame settings for CMS:
                // beautify the status string
                if (!statusStr.isEmpty()) {
                    statusStr.prepend(QStringLiteral("<i>"));
                    statusStr.append(QStringLiteral("</i>"));
                }

                // special color handling: S/MIME uses only green/yellow/red.
                switch (frameColor) {
                case SIG_FRAME_COL_RED:
                    mClass = QStringLiteral("signErr");//"signCMSRed";
                    onlyShowKeyURL = true;
                    break;
                case SIG_FRAME_COL_YELLOW:
                    if (mBlock.technicalProblem) {
                        mClass = QStringLiteral("signWarn");
                    } else {
                        mClass = QStringLiteral("signOkKeyBad");    //"signCMSYellow";
                    }
                    break;
                case SIG_FRAME_COL_GREEN:
                    mClass = QStringLiteral("signOkKeyOk");//"signCMSGreen";
                    // extra hint for green case
                    // that email addresses in DN do not match fromAddress
                    QString greenCaseWarning;
                    QString msgFrom(KEmailAddress::extractEmailAddress(mFromAddress));
                    QString certificate;
                    if (mBlock.keyId.isEmpty()) {
                        certificate = i18n("certificate");
                    } else {
                        certificate = startKeyHREF + i18n("certificate") + QStringLiteral("</a>");
                    }
                    if (!blockAddrs.empty()) {
                        if (!blockAddrs.contains(msgFrom, Qt::CaseInsensitive)) {
                            greenCaseWarning =
                                QStringLiteral("<u>") +
                                i18nc("Start of warning message.", "Warning:") +
                                QStringLiteral("</u> ") +
                                i18n("Sender's mail address is not stored in the %1 used for signing.",
                                     certificate) +
                                QStringLiteral("<br />") +
                                i18n("sender: ") +
                                msgFrom +
                                QStringLiteral("<br />") +
                                i18n("stored: ");
                            // We cannot use Qt's join() function here but
                            // have to join the addresses manually to
                            // extract the mail addresses (without '<''>')
                            // before including it into our string:
                            bool bStart = true;
                            for (QStringList::ConstIterator it = blockAddrs.constBegin();
                                    it != blockAddrs.constEnd(); ++it) {
                                if (!bStart) {
                                    greenCaseWarning.append(QStringLiteral(", <br />&nbsp; &nbsp;"));
                                }
                                bStart = false;
                                greenCaseWarning.append(KEmailAddress::extractEmailAddress(*it));
                            }
                        }
                    } else {
                        greenCaseWarning =
                            QStringLiteral("<u>") +
                            i18nc("Start of warning message.", "Warning:") +
                            QStringLiteral("</u> ") +
                            i18n("No mail address is stored in the %1 used for signing, "
Laurent Montel's avatar
Laurent Montel committed
676
677
678
                                 "so we cannot compare it to the sender's address %2.",
                                 certificate,
                                 msgFrom);
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
                    }
                    if (!greenCaseWarning.isEmpty()) {
                        if (!statusStr.isEmpty()) {
                            statusStr.append(QStringLiteral("<br />&nbsp;<br />"));
                        }
                        statusStr.append(greenCaseWarning);
                    }
                    break;
                }

                QString frame = QStringLiteral("<table cellspacing=\"1\" cellpadding=\"1\" class=\"%1\">").arg(mClass) +
                                QStringLiteral("<tr class=\"%1H\"><td dir=\"%2\">").arg(mClass, dir());
                htmlStr += frame + beginVerboseSigstatHeader();
                simpleHtmlStr += frame;
                simpleHtmlStr += simpleHeader();
                if (mBlock.technicalProblem) {
                    htmlStr += mBlock.errorText;
                } else if (showKeyInfos) {
                    if (cannotCheckSignature) {
                        htmlStr += i18n("Not enough information to check "
                                        "signature. %1",
                                        keyWithWithoutURL);
                    } else {

                        if (mBlock.signer.isEmpty()) {
                            signer.clear();
                        } else {
                            if (!blockAddrs.empty()) {
                                const QUrl address = KEmailAddress::encodeMailtoUrl(blockAddrs.first());
Laurent Montel's avatar
Laurent Montel committed
708
                                signer = QStringLiteral("<a href=\"mailto:%1\">%2</a>").arg(QLatin1String(QUrl::toPercentEncoding(address.path())), signer);
709
710
711
712
713
714
                            }
                        }

                        if (mBlock.keyId.isEmpty()) {
                            if (signer.isEmpty() || onlyShowKeyURL) {
                                htmlStr += i18n("Message was signed with unknown key.");
Laurent Montel's avatar
Laurent Montel committed
715
                            } else {
716
                                htmlStr += i18n("Message was signed by %1.", signer);
Laurent Montel's avatar
Laurent Montel committed
717
                            }
718
719
720
721
                        } else {
                            QDateTime created = mBlock.creationTime;
                            if (created.isValid()) {
                                if (signer.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
722
                                    if (onlyShowKeyURL) {
723
                                        htmlStr += i18n("Message was signed with key %1.", keyWithWithoutURL);
Laurent Montel's avatar
Laurent Montel committed
724
                                    } else
725
726
727
728
                                        htmlStr += i18n("Message was signed on %1 with key %2.",
                                                        QLocale::system().toString(created, QLocale::ShortFormat),
                                                        keyWithWithoutURL);
                                } else {
Laurent Montel's avatar
Laurent Montel committed
729
                                    if (onlyShowKeyURL) {
730
                                        htmlStr += i18n("Message was signed with key %1.", keyWithWithoutURL);
Laurent Montel's avatar
Laurent Montel committed
731
                                    } else
732
733
734
735
736
737
                                        htmlStr += i18n("Message was signed by %3 on %1 with key %2",
                                                        QLocale::system().toString(created, QLocale::ShortFormat),
                                                        keyWithWithoutURL,
                                                        signer);
                                }
                            } else {
Laurent Montel's avatar
Laurent Montel committed
738
                                if (signer.isEmpty() || onlyShowKeyURL) {
739
                                    htmlStr += i18n("Message was signed with key %1.", keyWithWithoutURL);
Laurent Montel's avatar
Laurent Montel committed
740
                                } else
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
                                    htmlStr += i18n("Message was signed by %2 with key %1.",
                                                    keyWithWithoutURL,
                                                    signer);
                            }
                        }
                    }
                    htmlStr += QStringLiteral("<br />");
                    if (!statusStr.isEmpty()) {
                        htmlStr += QStringLiteral("&nbsp;<br />");
                        htmlStr += i18n("Status: ");
                        htmlStr += statusStr;
                    }
                } else {
                    htmlStr += statusStr;
                }
                frame = QStringLiteral("</td></tr><tr class=\"%1B\"><td>").arg(mClass);
                htmlStr += endVerboseSigstatHeader(mBlock) + frame;
                simpleHtmlStr += frame;

            } else {

Sandro Knauß's avatar
Sandro Knauß committed
762
                QString content;
763
764
765
766
                // old frame settings for PGP:

                if (mBlock.signer.isEmpty() || mBlock.technicalProblem) {
                    mClass = QStringLiteral("signWarn");
Sandro Knauß's avatar
Sandro Knauß committed
767

768
                    if (mBlock.technicalProblem) {
Sandro Knauß's avatar
Sandro Knauß committed
769
                        content = mBlock.errorText;
770
771
772
773
                    } else {
                        if (!mBlock.keyId.isEmpty()) {
                            QDateTime created = mBlock.creationTime;
                            if (created.isValid())
Sandro Knauß's avatar
Sandro Knauß committed
774
775
776
                                content = i18n("Message was signed on %1 with unknown key %2.",
                                               QLocale::system().toString(created, QLocale::ShortFormat),
                                               keyWithWithoutURL);
777
                            else
Sandro Knauß's avatar
Sandro Knauß committed
778
779
                                content = i18n("Message was signed with unknown key %1.",
                                               keyWithWithoutURL);
780
                        } else {
Sandro Knauß's avatar
Sandro Knauß committed
781
                            content = i18n("Message was signed with unknown key.");
782
                        }
Sandro Knauß's avatar
Sandro Knauß committed
783
784
                        content += QStringLiteral("<br />");
                        content += i18n("The validity of the signature cannot be "
785
786
                                        "verified.");
                        if (!statusStr.isEmpty()) {
Sandro Knauß's avatar
Sandro Knauß committed
787
788
789
790
791
                            content += QStringLiteral("<br />");
                            content += i18n("Status: ");
                            content += QStringLiteral("<i>");
                            content += statusStr;
                            content += QStringLiteral("</i>");
792
793
794
795
796
                        }
                    }
                } else {
                    // HTMLize the signer's user id and create mailto: link
                    signer = MessageCore::StringUtil::quoteHtmlChars(signer, true);
Sandro Knauß's avatar
Sandro Knauß committed
797
                    signer = QStringLiteral("<a href=\"mailto:%1\">%1</a>").arg(signer);
798
799
800
801
802
803
804
805
806

                    if (mBlock.isGoodSignature) {
                        if (mBlock.keyTrust < GpgME::Signature::Marginal) {
                            mClass = QStringLiteral("signOkKeyBad");
                        } else {
                            mClass = QStringLiteral("signOkKeyOk");
                        }

                        if (!mBlock.keyId.isEmpty())
Sandro Knauß's avatar
Sandro Knauß committed
807
                            content = i18n("Message was signed by %2 (Key ID: %1).",
Laurent Montel's avatar
Laurent Montel committed
808
809
                                           keyWithWithoutURL,
                                           signer);
810
                        else {
Sandro Knauß's avatar
Sandro Knauß committed
811
                            content = i18n("Message was signed by %1.", signer);
812
                        }
Sandro Knauß's avatar
Sandro Knauß committed
813
                        content += QStringLiteral("<br />");
814
815
816

                        switch (mBlock.keyTrust) {
                        case GpgME::Signature::Unknown:
Sandro Knauß's avatar
Sandro Knauß committed
817
                            content += i18n("The signature is valid, but the key's "
818
819
820
                                            "validity is unknown.");
                            break;
                        case GpgME::Signature::Marginal:
Sandro Knauß's avatar
Sandro Knauß committed
821
                            content += i18n("The signature is valid and the key is "
822
823
824
                                            "marginally trusted.");
                            break;
                        case GpgME::Signature::Full:
Sandro Knauß's avatar
Sandro Knauß committed
825
                            content += i18n("The signature is valid and the key is "
826
827
828
                                            "fully trusted.");
                            break;
                        case GpgME::Signature::Ultimate:
Sandro Knauß's avatar
Sandro Knauß committed
829
                            content += i18n("The signature is valid and the key is "
830
831
832
                                            "ultimately trusted.");
                            break;
                        default:
Sandro Knauß's avatar
Sandro Knauß committed
833
                            content += i18n("The signature is valid, but the key is "
834
835
836
837
838
839
                                            "untrusted.");
                        }
                    } else {
                        mClass = QStringLiteral("signErr");

                        if (!mBlock.keyId.isEmpty())
Sandro Knauß's avatar
Sandro Knauß committed
840
                            content = i18n("Message was signed by %2 (Key ID: %1).",
Laurent Montel's avatar
Laurent Montel committed
841
842
                                           keyWithWithoutURL,
                                           signer);
843
                        else {
Sandro Knauß's avatar
Sandro Knauß committed
844
                            content = i18n("Message was signed by %1.", signer);
845
                        }
Sandro Knauß's avatar
Sandro Knauß committed
846
847
                        content += QStringLiteral("<br />");
                        content += i18n("Warning: The signature is bad.");
848
849
                    }
                }
Sandro Knauß's avatar
Sandro Knauß committed
850
851
852
853
854
855
856
857
858
859
860
                const QString beginFrame = QStringLiteral("<table cellspacing=\"1\" cellpadding=\"1\" class=\"%1\">").arg(mClass) +
                                           QStringLiteral("<tr class=\"%1H\"><td dir=\"%2\">").arg(mClass, dir());
                const QString endFrame = QStringLiteral("</td></tr><tr class=\"%1B\"><td>").arg(mClass);

                htmlStr += beginFrame + beginVerboseSigstatHeader();
                htmlStr += content;
                htmlStr += endVerboseSigstatHeader(mBlock) + endFrame;

                simpleHtmlStr += beginFrame;
                simpleHtmlStr += simpleHeader();
                simpleHtmlStr += endFrame;
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
            }
        }

        if (mSource->showSignatureDetails()) {
            mWriter->queue(htmlStr);
        }
        mWriter->queue(simpleHtmlStr);
    }
}

void SignedBlock::internalExit()
{
    if (!entered) {
        return;
    }

    mWriter->queue(QStringLiteral("</td></tr>"
Laurent Montel's avatar
Laurent Montel committed
878
879
                                  "<tr class=\"%1H\"><td dir=\"%2\">%3</td></tr>"
                                  "</table>").arg(mClass, dir(), i18n("End of signed message")));
880
881
882
    entered = false;
}

883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
AttachmentMarkBlock::AttachmentMarkBlock(MessageViewer::HtmlWriter *writer, KMime::Content *node)
    : mNode(node)
    , mWriter(writer)
{
    internalEnter();
}

AttachmentMarkBlock::~AttachmentMarkBlock()
{
    internalExit();
}

void AttachmentMarkBlock::internalEnter()
{
    if (mWriter && !entered) {
        const QString index = mNode->index().toString();
        mWriter->queue(QStringLiteral("<a name=\"att%1\"></a>").arg(index));
        mWriter->queue(QStringLiteral("<div id=\"attachmentDiv%1\">\n").arg(index));
        entered = true;
    }
}

void AttachmentMarkBlock::internalExit()
{
    if (!entered) {
        return;
    }

    mWriter->queue(QStringLiteral("</div>"));
    entered = false;
}

TextBlock::TextBlock(MessageViewer::HtmlWriter *writer, MessageViewer::NodeHelper *nodeHelper, KMime::Content *node, bool link)
    : mWriter(writer)
    , mNodeHelper(nodeHelper)
    , mNode(node)
    , mLink(link)
{
    internalEnter();
}

TextBlock::~TextBlock()
{
    internalExit();
}

void TextBlock::internalEnter()
{
    if (!mWriter || entered) {
        return;
    }
    entered = true;

    const QString label = MessageCore::StringUtil::quoteHtmlChars(NodeHelper::fileName(mNode), true);

    const QString comment =
        MessageCore::StringUtil::quoteHtmlChars(mNode->contentDescription()->asUnicodeString(), true);

    mWriter->queue(QLatin1String("<table cellspacing=\"1\" class=\"textAtm\">"
942
                                 "<tr class=\"textAtmH\"><td dir=\"") + dir() + QLatin1String("\">"));
943
944
945
946
947
948
949
950
951
    if (!mLink)
        mWriter->queue(QLatin1String("<a href=\"") + mNodeHelper->asHREF(mNode, QStringLiteral("body")) + QLatin1String("\">")
                       + label + QLatin1String("</a>"));
    else {
        mWriter->queue(label);
    }
    if (!comment.isEmpty()) {
        mWriter->queue(QLatin1String("<br/>") + comment);
    }
Laurent Montel's avatar
Laurent Montel committed
952
    mWriter->queue(QStringLiteral("</td></tr><tr class=\"textAtmB\"><td>"));
953
954
955
956
957
958
959
960
961
962
963
964
965
}

void TextBlock::internalExit()
{
    if (!entered) {
        return;
    }

    entered = false;

    mWriter->queue(QStringLiteral("</td></tr></table>"));
}

Laurent Montel's avatar
Laurent Montel committed
966
HTMLWarnBlock::HTMLWarnBlock(HtmlWriter *writer, const QString &msg)
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
    : mWriter(writer)
    , mMsg(msg)
{
    internalEnter();
}

HTMLWarnBlock::~HTMLWarnBlock()
{
    internalExit();
}

void HTMLWarnBlock::internalEnter()
{
    if (!mWriter || entered) {
        return;
    }
    entered = true;

    if (!mMsg.isEmpty()) {
        mWriter->queue(QStringLiteral("<div class=\"htmlWarn\">\n"));
        mWriter->queue(mMsg);
        mWriter->queue(QStringLiteral("</div><br/><br/>"));
    }

    mWriter->queue(QStringLiteral("<div style=\"position: relative\">\n"));
}

void HTMLWarnBlock::internalExit()
{
    if (!entered) {
        return;
    }

    entered = false;
For faster browsing, not all history is shown. View entire blame