stringutiltest.cpp 35 KB
Newer Older
1
/* SPDX-FileCopyrightText: 2009 Thomas McGuire <mcguire@kde.org>
2

3
   SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
4
5
6
7
8
*/
#include "stringutiltest.h"

#include "../src/utils/stringutil.h"

Laurent Montel's avatar
Laurent Montel committed
9
#include <QTest>
10
11
12
#include <QUrl>
using namespace MessageCore;

13
QTEST_MAIN(StringUtilTest)
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#define lineLength 40

void StringUtilTest::test_SmartQuote()
{
    QFETCH(QString, originalString);
    QFETCH(QString, quotedString);

    QEXPECT_FAIL("hard linebreak", "Currently no way to differentiate between hard and soft line breaks", Continue);
    const QString result = StringUtil::smartQuote(originalString, lineLength);
    const QStringList resultList = result.split(QLatin1Char('\n'));
    const QStringList expectedList = quotedString.split(QLatin1Char('\n'));
    qDebug() << "result  :" << resultList;
    qDebug() << "expected:" << expectedList;
    QCOMPARE(resultList, expectedList);
Laurent Montel's avatar
Laurent Montel committed
28
    // QCOMPARE( result, quotedString );
29
30
31
32
33
34
35
}

void StringUtilTest::test_SmartQuote_data()
{
    QTest::addColumn<QString>("originalString");
    QTest::addColumn<QString>("quotedString");

36
    QTest::newRow("") << ""
Laurent Montel's avatar
Laurent Montel committed
37
                      << "";
38

39
40
41
42
43
44
45
46
47
48
49
50
51
    QTest::newRow("1") << "Some short text"
                       << "Some short text";

    //                                                               40
    //                                                               ↓
    QTest::newRow("2") << "Some much longer text that exceeds our limit by far."
                       << "Some much longer text that exceeds our\nlimit by far.";

    QTest::newRow("3") << " Space at start."
                       << " Space at start.";

    //                                                               40
    //                                                               ↓
Yuri Chornoivan's avatar
Yuri Chornoivan committed
52
53
    QTest::newRow("4") << " Space at start, but also two lines in this long sentence."
                       << " Space at start, but also two lines in\nthis long sentence.";
54
55
56
57
58

    QTest::newRow("5") << " Space at start and end. "
                       << " Space at start and end.";

    QTest::newRow("6") << "Space at end of pre-broken line. \n"
Laurent Montel's avatar
Laurent Montel committed
59
                          "Yet another line of text."
60
                       << "Space at end of pre-broken line.\n"
Laurent Montel's avatar
Laurent Montel committed
61
                          "Yet another line of text.";
62
63
64
65

    //                                                               40
    //                                                               ↓
    QTest::newRow("7") << "Long long line, followed by another line starting with a space.\n"
Laurent Montel's avatar
Laurent Montel committed
66
                          " Starts with a space."
67
                       << "Long long line, followed by another line\n"
Laurent Montel's avatar
Laurent Montel committed
68
69
                          "starting with a space. Starts with a\n"
                          "space.";
70
71
72
73
74
75
76
77
78

    //                                                               40
    //                                                               ↓
    QTest::newRow("8") << "Two lines that don't need to be\nchanged in any way."
                       << "Two lines that don't need to be\nchanged in any way.";

    //                                                               40
    //                                                               ↓
    QTest::newRow("9") << "Many lines.\n"
Laurent Montel's avatar
Laurent Montel committed
79
80
81
                          "Only one needs to be broken.\n"
                          "This is the very much too long line that needs to be broken.\n"
                          "This line is ok again."
82
                       << "Many lines.\n"
Laurent Montel's avatar
Laurent Montel committed
83
84
85
86
                          "Only one needs to be broken.\n"
                          "This is the very much too long line that\n"
                          "needs to be broken. This line is ok\n"
                          "again.";
87
88
89
90
91

    //                                                                40
    //                                                                ↓
    QTest::newRow("10") << "> >Very long quoted line, that is very very long"
                        << "> >Very long quoted line, that is very\n"
Laurent Montel's avatar
Laurent Montel committed
92
                           "> >very long";
93
94
95
96
97

    //                                                                40
    //                                                                ↓
    QTest::newRow("11") << "> > Very long quoted line, that is very very long"
                        << "> > Very long quoted line, that is very\n"
Laurent Montel's avatar
Laurent Montel committed
98
                           "> > very long";
99
100
101
102

    //                                                                40
    //                                                                ↓
    QTest::newRow("12") << "> > Very long quoted line, that is very very long. \n"
Laurent Montel's avatar
Laurent Montel committed
103
                           "> > Another line here."
104
                        << "> > Very long quoted line, that is very\n"
Laurent Montel's avatar
Laurent Montel committed
105
                           "> > very long. Another line here.";
106
107
108
109

    //                                                                40
    //                                                                ↓
    QTest::newRow("13") << "> > Very long quoted line, that is very very long. \n"
Laurent Montel's avatar
Laurent Montel committed
110
111
                           "Unquoted line, for a change.\n"
                           "> > Another line here."
112
                        << "> > Very long quoted line, that is very\n"
Laurent Montel's avatar
Laurent Montel committed
113
114
115
116
                           "> > very long.\n"
                           "Unquoted line, for a change.\n"
                           "\n"
                           "> > Another line here.";
117
118
119
120

    //                                                                40
    //                                                                ↓
    QTest::newRow("14") << "> Quote level 1 with long long long long text, that is long.\n"
Laurent Montel's avatar
Laurent Montel committed
121
122
123
124
                           "> Quote level 1 still.\n"
                           "> > Quote level 2 now, also with a long long long long text.\n"
                           "> > Quote level 2 still.\n"
                           "No quotes."
125
                        << "> Quote level 1 with long long long long\n"
Laurent Montel's avatar
Laurent Montel committed
126
127
128
129
130
131
132
133
                           "> text, that is long. Quote level 1\n"
                           "> still.\n"
                           "> \n"
                           "> > Quote level 2 now, also with a long\n"
                           "> > long long long text. Quote level 2\n"
                           "> > still.\n"
                           "\n"
                           "No quotes.";
134
135
136
137

    //                                                                40
    //                                                                ↓
    QTest::newRow("15") << "Some much longer text that exceeds our limit by far.\n"
Laurent Montel's avatar
Laurent Montel committed
138
139
                           "\n"
                           "Line after an empty one."
140
                        << "Some much longer text that exceeds our\n"
Laurent Montel's avatar
Laurent Montel committed
141
142
143
                           "limit by far.\n"
                           "\n"
                           "Line after an empty one.";
144
145
146
147
148

    // Make sure the "You wrote:" line is not broken, that would look strange
    //                                                                40
    //                                                                ↓
    QTest::newRow("16") << "Yesterday, Hans Martin Ulrich Franz August wrote:\n"
Laurent Montel's avatar
Laurent Montel committed
149
                           "> Bla Bla Bla Bla..."
150
                        << "Yesterday, Hans Martin Ulrich Franz August wrote:\n"
Laurent Montel's avatar
Laurent Montel committed
151
                           "> Bla Bla Bla Bla...";
152
153
154
155

    //                                                                40
    //                                                                ↓
    QTest::newRow("17") << "Yesterday, Hans Martin Ulrich Franz August wrote:\n"
Laurent Montel's avatar
Laurent Montel committed
156
157
                           "\n"
                           "> Bla Bla Bla Bla..."
158
                        << "Yesterday, Hans Martin Ulrich Franz August wrote:\n"
Laurent Montel's avatar
Laurent Montel committed
159
                           "> Bla Bla Bla Bla...";
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177

    // This test shows a fundamental flaw when breaking lines: The table header gets broken,
    // which is ok. However, the following line is appended to the table header, which leads
    // the data columns and the table header to by not aligned anymore.
    // The problem is that the linebreak after the table header is supposed to be a hard line break,
    // but there is no way to know that, so we treat it as a soft line break instead.
    //
    // We can't treat every linebreak as hard linebreaks, as quoting natural text would look strange then.
    // The problem here is that the sender word-wraps the email with hard linebreaks.
    // This is only possible to fix correctly by supporting "flowed" text, as per RFC 2646
    //
    // (this is marked as QEXPECT_FAIL above)
    //
    // The solution would be to let the caller remove soft linebreaks manually (as only the caller
    // can now), and let smartQuote() treat all linebreaks as hard linebreaks, which would fix this.
    //                                                                           40
    //                                                                            ↓
    QTest::newRow("hard linebreak") << "==== Date ======== Amount ======= Type ======\n"
Laurent Montel's avatar
Laurent Montel committed
178
                                       "   12.12.09          5            Car        \n"
179
                                    << "==== Date ======== Amount ======= Type\n"
Laurent Montel's avatar
Laurent Montel committed
180
181
                                       "======\n"
                                       "   12.12.09          5            Car        \n";
182
183
184
185
}

void StringUtilTest::test_signatureStripping()
{
Laurent Montel's avatar
Laurent Montel committed
186
    // QStringList tests;
187
    const QString test1 = QStringLiteral(
Laurent Montel's avatar
Laurent Montel committed
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
        "text1\n"
        "-- \n"
        "Signature Block1\n"
        "Signature Block1\n\n"
        "> text2\n"
        "> -- \n"
        "> Signature Block 2\n"
        "> Signature Block 2\n"
        ">> text3 -- not a signature block\n"
        ">> text3\n"
        ">>> text4\n"
        "> -- \n"
        "> Signature Block 4\n"
        "> Signature Block 4\n"
        ">>-------------\n"
        ">>-- text5 --\n"
        ">>-------------------\n"
        ">>-- \n"
        ">>\n"
        ">> Signature Block 5\n"
        "text6\n"
        "-- \n"
        "Signature Block 6\n");
211
212

    const QString test1Result = QStringLiteral(
Laurent Montel's avatar
Laurent Montel committed
213
214
215
216
217
218
219
220
221
        "text1\n"
        "> text2\n"
        ">> text3 -- not a signature block\n"
        ">> text3\n"
        ">>> text4\n"
        ">>-------------\n"
        ">>-- text5 --\n"
        ">>-------------------\n"
        "text6\n");
222
223
224
225

    QCOMPARE(StringUtil::stripSignature(test1), test1Result);

    const QString test2 = QStringLiteral(
Laurent Montel's avatar
Laurent Montel committed
226
227
228
229
230
231
232
233
234
        "text1\n"
        "> text2\n"
        ">> text3 -- not a signature block\n"
        ">> text3\n"
        ">>> text4\n"
        ">>-------------\n"
        ">>-- text5 --\n"
        ">>-------------------\n"
        "text6\n");
235
236
237
238
239

    // No actual signature - should stay the same
    QCOMPARE(StringUtil::stripSignature(test2), test2);

    const QString test3 = QStringLiteral(
Laurent Montel's avatar
Laurent Montel committed
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
        "text1\n"
        "-- \n"
        "Signature Block1\n"
        "Signature Block1\n\n"
        ">text2\n"
        ">-- \n"
        ">Signature Block 2\n"
        ">Signature Block 2\n"
        "> >text3\n"
        "> >text3\n"
        "> >-- \n"
        ">>Not Signature Block 3\n"
        "> > Not Signature Block 3\n"
        ">text4\n"
        ">-- \n"
        ">Signature Block 4\n"
        ">Signature Block 4\n"
        "text5\n"
        "-- \n"
        "Signature Block 5");
260
261

    const QString test3Result = QStringLiteral(
Laurent Montel's avatar
Laurent Montel committed
262
263
264
265
266
267
268
269
        "text1\n"
        ">text2\n"
        "> >text3\n"
        "> >text3\n"
        ">>Not Signature Block 3\n"
        "> > Not Signature Block 3\n"
        ">text4\n"
        "text5\n");
270
271
272
273

    QCOMPARE(StringUtil::stripSignature(test3), test3Result);

    const QString test4 = QStringLiteral(
Laurent Montel's avatar
Laurent Montel committed
274
275
276
277
278
279
280
281
282
283
284
285
        "Text 1\n"
        "-- \n"
        "First sign\n\n\n"
        "> From: bla\n"
        "> Texto 2\n\n"
        "> Aqui algo de texto.\n\n"
        ">> --\n"
        ">> Not Signature Block 2\n\n"
        "> Adios\n\n"
        ">> Texto 3\n\n"
        ">> --\n"
        ">> Not Signature block 3\n");
286
287

    const QString test4Result = QStringLiteral(
Laurent Montel's avatar
Laurent Montel committed
288
289
290
291
292
293
294
295
296
297
        "Text 1\n"
        "> From: bla\n"
        "> Texto 2\n\n"
        "> Aqui algo de texto.\n\n"
        ">> --\n"
        ">> Not Signature Block 2\n\n"
        "> Adios\n\n"
        ">> Texto 3\n\n"
        ">> --\n"
        ">> Not Signature block 3\n");
298
299
300
301

    QCOMPARE(StringUtil::stripSignature(test4), test4Result);

    const QString test5 = QStringLiteral(
Laurent Montel's avatar
Laurent Montel committed
302
303
304
305
306
307
        "-- \n"
        "-- ACME, Inc\n"
        "-- Joe User\n"
        "-- PHB\n"
        "-- Tel.: 555 1234\n"
        "--");
308
309
310
311

    QCOMPARE(StringUtil::stripSignature(test5), QString());

    const QString test6 = QStringLiteral(
Laurent Montel's avatar
Laurent Montel committed
312
313
314
315
316
317
318
319
320
        "Text 1\n\n\n\n"
        "> From: bla\n"
        "> Texto 2\n\n"
        "> Aqui algo de texto.\n\n"
        ">> Not Signature Block 2\n\n"
        "> Adios\n\n"
        ">> Texto 3\n\n"
        ">> --\n"
        ">> Not Signature block 3\n");
321
322
323
324
325
326
327
328
329

    // Again, no actual signature in here
    QCOMPARE(StringUtil::stripSignature(test6), test6);
}

void StringUtilTest::test_stripOffMessagePrefixBenchmark()
{
    const QString subject = QStringLiteral("Fwd: Hello World Subject");
    QBENCHMARK {
Laurent Montel's avatar
Laurent Montel committed
330
        QString result = StringUtil::stripOffPrefixes(subject);
Laurent Montel's avatar
Laurent Montel committed
331
        Q_UNUSED(result)
332
333
334
335
336
337
338
339
    }
}

void StringUtilTest::test_parseMailtoUrl_data()
{
    QTest::addColumn<QString>("mailToUrl");
    QTest::addColumn<bool>("toIsNotEmpty");
    QTest::addColumn<int>("numberOfTo");
340
    QTest::addColumn<int>("numberElement");
341

342
343
344
345
346
    QTest::newRow("1 mailto") << QStringLiteral("mailto:foo@kde.org") << true << 1 << 1;
    QTest::newRow("invalid (not mailto)") << QStringLiteral("http://www.kde.org") << false << 0 << 0;
    QTest::newRow("invalid (no email address") << QStringLiteral("mailto:") << false << 0 << 0;
    QTest::newRow("2 address") << QStringLiteral("mailto:foo@kde.org?to=foo2@kde.org") << true << 2 << 1;
    QTest::newRow("invalid") << QStringLiteral("fookde.org") << false << 0 << 0;
347
348
349
350
351
352
353
}

void StringUtilTest::test_parseMailtoUrl()
{
    QFETCH(QString, mailToUrl);
    QFETCH(bool, toIsNotEmpty);
    QFETCH(int, numberOfTo);
354
    QFETCH(int, numberElement);
355
356

    QUrl url(mailToUrl);
357
    auto list = StringUtil::parseMailtoUrl(url);
Laurent Montel's avatar
Laurent Montel committed
358
    // qDebug() << " list "<< list;
359
360
361
    QCOMPARE(list.count(), numberElement);
    if (numberOfTo > 0) {
        QCOMPARE(!list.at(0).second.isEmpty(), toIsNotEmpty);
362
        QCOMPARE(list.at(0).second.split(QLatin1String(", "), Qt::SkipEmptyParts).count(), numberOfTo);
363
    }
364
365
366
367
}

void StringUtilTest::test_parseMailtoUrlExtra()
{
Laurent Montel's avatar
Laurent Montel committed
368
369
    const QByteArray ba("mailto:someone@example.com?subject=This%20is%20the%20subject&cc=someone_else@example.com&body=This%20is%20the%20body");
    QUrl url = QUrl(QUrl::fromPercentEncoding(ba));
Laurent Montel's avatar
Laurent Montel committed
370
    QVector<QPair<QString, QString>> data = StringUtil::parseMailtoUrl(url);
371
    QCOMPARE(data.size(), 4);
372
373
374
375
376
377
378
379
380
381
382
    for (int i = 0; i < 4; ++i) {
        if (data.at(i).first == QLatin1String("to")) {
            QCOMPARE(data.at(i).second, QLatin1String("someone@example.com"));
        } else if (data.at(i).first == QLatin1String("subject")) {
            QCOMPARE(data.at(i).second, QLatin1String("This is the subject"));
        } else if (data.at(i).first == QLatin1String("cc")) {
            QCOMPARE(data.at(i).second, QLatin1String("someone_else@example.com"));
        } else if (data.at(i).first == QLatin1String("body")) {
            QCOMPARE(data.at(i).second, QLatin1String("This is the body"));
        }
    }
383
384
}

385
386
387
388
void StringUtilTest::test_parseMailToWithUtf8Encoded()
{
    const QByteArray ba("mailto:=?utf-8?B?TWnFgm9zeg==?= Vo %3Craco.cki@foo.com%3E");
    QUrl url = QUrl(QUrl::fromPercentEncoding(ba));
Laurent Montel's avatar
Laurent Montel committed
389
    QVector<QPair<QString, QString>> data = StringUtil::parseMailtoUrl(url);
390
391
392
393
394
395
396
397
    QCOMPARE(data.size(), 1);
    for (int i = 0; i < 1; ++i) {
        if (data.at(i).first == QLatin1String("to")) {
            QCOMPARE(data.at(i).second, QString::fromUtf8("Mi\u0142osz Vo <raco.cki@foo.com>"));
        }
    }
}

Laurent Montel's avatar
Laurent Montel committed
398
399
400
401
void StringUtilTest::test_parseMailToWithUtf8QuotedEncoded()
{
    const QByteArray ba("mailto:=?utf-8?q?foo_Cen_=3Cbla=2Ecete=40kde=2Ecom=3E=2C_Kile_Debut_=3Ckile=2Edebut=40foo=2Ecom?=");
    QUrl url = QUrl(QUrl::fromPercentEncoding(ba));
Laurent Montel's avatar
Laurent Montel committed
402
    QVector<QPair<QString, QString>> data = StringUtil::parseMailtoUrl(url);
Laurent Montel's avatar
Laurent Montel committed
403
404
405
406
407
408
409
410
    QCOMPARE(data.size(), 1);
    for (int i = 0; i < 1; ++i) {
        if (data.at(i).first == QLatin1String("to")) {
            QCOMPARE(data.at(i).second, QString::fromUtf8("foo Cen <bla.cete@kde.com>, Kile Debut <kile.debut@foo.com"));
        }
    }
}

411
412
void StringUtilTest::test_parseMailToBug366981()
{
Laurent Montel's avatar
Laurent Montel committed
413
414
    const QByteArray ba(QByteArrayLiteral("mailto:test@test.com?subject=test&body=line1%0D%0Aline2"));
    QUrl urlDecoded(QUrl::fromPercentEncoding(ba));
Laurent Montel's avatar
Laurent Montel committed
415
    QVector<QPair<QString, QString>> data = StringUtil::parseMailtoUrl(urlDecoded);
416
    QCOMPARE(data.size(), 3);
417
418
419
420
421
422
423
424
425
426
427
428
    for (int i = 0; i < 3; ++i) {
        if (data.at(i).first == QLatin1String("to")) {
            QCOMPARE(i, 0);
            QCOMPARE(data.at(i).second, QLatin1String("test@test.com"));
        } else if (data.at(i).first == QLatin1String("subject")) {
            QCOMPARE(i, 1);
            QCOMPARE(data.at(i).second, QLatin1String("test"));
        } else if (data.at(i).first == QLatin1String("body")) {
            QCOMPARE(i, 2);
            QCOMPARE(data.at(i).second, QLatin1String("line1\r\nline2"));
        }
    }
429
430
}

431
432
void StringUtilTest::test_parseDuplicateQueryItems()
{
Laurent Montel's avatar
Laurent Montel committed
433
434
    const QByteArray ba(QByteArrayLiteral("mailto:test@test.com?subject=test&body=line1%0D%0Aline2&cc=someone_else@example.com&cc=someone_else2@example.com"));
    QUrl urlDecoded(QUrl::fromPercentEncoding(ba));
Laurent Montel's avatar
Laurent Montel committed
435
    QVector<QPair<QString, QString>> values = StringUtil::parseMailtoUrl(urlDecoded);
436
    QCOMPARE(values.size(), 5);
437
438
    int valueCC = 0;
    for (int i = 0; i < values.size(); ++i) {
439
        if (values.at(i).first == QLatin1String("to")) {
440
            QCOMPARE(values.at(i).second, QLatin1String("test@test.com"));
441
        } else if (values.at(i).first == QLatin1String("subject")) {
442
            QCOMPARE(values.at(i).second, QLatin1String("test"));
443
        } else if (values.at(i).first == QLatin1String("body")) {
444
            QCOMPARE(values.at(i).second, QLatin1String("line1\r\nline2"));
445
        } else if (values.at(i).first == QLatin1String("cc")) {
446
447
            const QString ccVal = values.at(i).second;
            valueCC++;
448
449
450
451
452
            if ((ccVal != QLatin1String("someone_else@example.com")) && (ccVal != QLatin1String("someone_else2@example.com"))) {
                QVERIFY(false);
            }
        }
    }
453
454
455
456
457
    QCOMPARE(valueCC, 2);
}

void StringUtilTest::test_parseMAilToBug402378()
{
Laurent Montel's avatar
Laurent Montel committed
458
459
460
461
    const QByteArray ba(
        QByteArrayLiteral("mailto:?body=infotbm.com "
                          "https://www.infotbm.com/fr/routes/id=-0.624162|44.849958&type=address/"
                          "datetime=20181226T143038&id=stop_area:TBT:SA:HTLEV&time_type=arrival&type=stop_area/0&subject=Votre itineraire avec TBM"));
Laurent Montel's avatar
Laurent Montel committed
462
    QUrl urlDecoded(QUrl::fromPercentEncoding(ba));
Laurent Montel's avatar
Laurent Montel committed
463
    QVector<QPair<QString, QString>> data = StringUtil::parseMailtoUrl(urlDecoded);
464
465
    QCOMPARE(data.size(), 6);
    QCOMPARE(data.at(0).first, QLatin1String("body"));
Laurent Montel's avatar
Laurent Montel committed
466
    QCOMPARE(data.at(0).second, QLatin1String("infotbm.com https://www.infotbm.com/fr/routes/id=-0.624162|44.849958"));
467
468
469
470
471
472
473
474
475
476
    QCOMPARE(data.at(1).first, QLatin1String("type"));
    QCOMPARE(data.at(1).second, QLatin1String("address/datetime=20181226T143038"));
    QCOMPARE(data.at(2).first, QLatin1String("id"));
    QCOMPARE(data.at(2).second, QLatin1String("stop_area:TBT:SA:HTLEV"));
    QCOMPARE(data.at(3).first, QLatin1String("time_type"));
    QCOMPARE(data.at(3).second, QLatin1String("arrival"));
    QCOMPARE(data.at(4).first, QLatin1String("type"));
    QCOMPARE(data.at(4).second, QLatin1String("stop_area/0"));
    QCOMPARE(data.at(5).first, QLatin1String("subject"));
    QCOMPARE(data.at(5).second, QLatin1String("Votre itineraire avec TBM"));
477
478
}

Laurent Montel's avatar
Laurent Montel committed
479
480
void StringUtilTest::test_parseMailToBug406208()
{
481
    {
Laurent Montel's avatar
Laurent Montel committed
482
483
        const QByteArray ba(
            QByteArrayLiteral("mailto:?body=http%3A%2F%2Fwww.lecourrierdelarchitecte.com%2Farticle_8428&subject=Le%20Courrier%20l'effet%20%23metoo%20%3F"));
Laurent Montel's avatar
Laurent Montel committed
484
        QUrl urlDecoded(QUrl::fromPercentEncoding(ba));
Laurent Montel's avatar
Laurent Montel committed
485
486
        // qDebug() << " urlDecoded" << urlDecoded.authority(QUrl::FullyDecoded);
        QVector<QPair<QString, QString>> data = StringUtil::parseMailtoUrl(urlDecoded);
487
488
        QCOMPARE(data.size(), 2);
        QCOMPARE(data.at(0).first, QLatin1String("body"));
Laurent Montel's avatar
Laurent Montel committed
489
        QCOMPARE(data.at(0).second, QLatin1String("http://www.lecourrierdelarchitecte.com/article_8428"));
490
491
492
493
        QCOMPARE(data.at(1).first, QLatin1String("subject"));
        QCOMPARE(data.at(1).second, QLatin1String("Le Courrier l'effet #metoo ?"));
    }
    {
Laurent Montel's avatar
Laurent Montel committed
494
        const QByteArray ba(QByteArrayLiteral(
Laurent Montel's avatar
Laurent Montel committed
495
            "mailto:?body=http%3A%2F%2Fwww.lecourrierdelarchitecte.com%2Farticle_8428%20%23%23bla&subject=Le%20Courrier%20l'effet%20%23metoo%20%3F"));
Laurent Montel's avatar
Laurent Montel committed
496
        QUrl urlDecoded(QUrl::fromPercentEncoding(ba));
Laurent Montel's avatar
Laurent Montel committed
497
498
        // qDebug() << " urlDecoded" << urlDecoded.authority(QUrl::FullyDecoded);
        QVector<QPair<QString, QString>> data = StringUtil::parseMailtoUrl(urlDecoded);
499
500
        QCOMPARE(data.size(), 2);
        QCOMPARE(data.at(0).first, QLatin1String("body"));
Laurent Montel's avatar
Laurent Montel committed
501
        QCOMPARE(data.at(0).second, QLatin1String("http://www.lecourrierdelarchitecte.com/article_8428 ##bla"));
502
503
504
        QCOMPARE(data.at(1).first, QLatin1String("subject"));
        QCOMPARE(data.at(1).second, QLatin1String("Le Courrier l'effet #metoo ?"));
    }
Laurent Montel's avatar
Laurent Montel committed
505
506
}

Laurent Montel's avatar
Laurent Montel committed
507
508
void StringUtilTest::test_parseMailToBug832795()
{
Laurent Montel's avatar
Laurent Montel committed
509
510
511
512
513
    const QByteArray ba(
        QByteArrayLiteral("mailto:832795@bugs.debian.org?In-Reply-To=%3C146974194340.26747.4814466130640572267.reportbug%40portux.lan.naturalnet.de%3E&subject="
                          "Re%3A%20kmail%3A%20unescaping%20mailto%3A%20links%20broken&body=On%20Thu%2C%2028%20Jul%202016References=%3C146974194340.26747."
                          "4814466130640572267.reportbug%40portux.lan.naturalnet.de%3Ebody=On%20Thu%2C%2028%20Jul%202016%2023%3A39%3A03%20%2B0200%20Dominik%"
                          "20George%20%3Cnik%40naturalnet.de%3E%20wrote%3A%0A%3E%20Package%3A%20kmail%0A%3E%20Version%3A%204%3A16.04.3-1%0A"));
Laurent Montel's avatar
Laurent Montel committed
514
    QUrl urlDecoded(QUrl::fromPercentEncoding(ba));
Laurent Montel's avatar
Laurent Montel committed
515
    QVector<QPair<QString, QString>> data = StringUtil::parseMailtoUrl(urlDecoded);
Laurent Montel's avatar
Laurent Montel committed
516
    QCOMPARE(data.size(), 4);
517
518
    QCOMPARE(data.at(0).first, QLatin1String("to"));
    QCOMPARE(data.at(0).second, QLatin1String("832795@bugs.debian.org"));
Laurent Montel's avatar
Laurent Montel committed
519
    QCOMPARE(data.at(1).first, QLatin1String("in-reply-to"));
520
521
522
523
    QCOMPARE(data.at(1).second, QLatin1String("<146974194340.26747.4814466130640572267.reportbug@portux.lan.naturalnet.de>"));
    QCOMPARE(data.at(2).first, QLatin1String("subject"));
    QCOMPARE(data.at(2).second, QLatin1String("Re: kmail: unescaping mailto: links broken"));
    QCOMPARE(data.at(3).first, QLatin1String("body"));
Laurent Montel's avatar
Laurent Montel committed
524
525
526
    QCOMPARE(data.at(3).second,
             QLatin1String("On Thu, 28 Jul 2016References=<146974194340.26747.4814466130640572267.reportbug@portux.lan.naturalnet.de>body=On Thu, 28 Jul 2016 "
                           "23:39:03 +0200 Dominik George <nik@naturalnet.de> wrote:\n> Package: kmail\n> Version: 4:16.04.3-1\n"));
Laurent Montel's avatar
Laurent Montel committed
527
528
}

529
530
void StringUtilTest::test_crashXdgemail()
{
Laurent Montel's avatar
Laurent Montel committed
531
532
    const QByteArray ba(QByteArrayLiteral("mailto:foo@kde.org?to=bar@kde.org&to=baz@kde.org"));
    QUrl urlDecoded(QUrl::fromPercentEncoding(ba));
Laurent Montel's avatar
Laurent Montel committed
533
    QVector<QPair<QString, QString>> values = StringUtil::parseMailtoUrl(urlDecoded);
534
535
536
537
538
539
540
541
    QCOMPARE(values.size(), 1);
    QCOMPARE(values.at(0).first, QLatin1String("to"));
    QCOMPARE(values.at(0).second, QLatin1String("foo@kde.org, bar@kde.org, baz@kde.org"));
}

void StringUtilTest::test_xdgemail()
{
    {
Laurent Montel's avatar
Laurent Montel committed
542
543
        const QByteArray ba(QByteArrayLiteral("mailto:foo@kde.org?to=bar@kde.org&to=baz@kde.org&cc=bli@kde.org"));
        QUrl urlDecoded(QUrl::fromPercentEncoding(ba));
Laurent Montel's avatar
Laurent Montel committed
544
        QVector<QPair<QString, QString>> values = StringUtil::parseMailtoUrl(urlDecoded);
545
546
547
548
549
550
551
        QCOMPARE(values.size(), 2);
        QCOMPARE(values.at(0).first, QLatin1String("to"));
        QCOMPARE(values.at(0).second, QLatin1String("foo@kde.org, bar@kde.org, baz@kde.org"));
        QCOMPARE(values.at(1).first, QLatin1String("cc"));
        QCOMPARE(values.at(1).second, QLatin1String("bli@kde.org"));
    }
    {
Laurent Montel's avatar
Laurent Montel committed
552
553
        const QByteArray ba(QByteArrayLiteral("mailto:foo@kde.org?to=bar@kde.org&to=baz@kde.org&cc=ss@kde.org&bcc=ccs@kde.org"));
        QUrl urlDecoded(QUrl::fromPercentEncoding(ba));
Laurent Montel's avatar
Laurent Montel committed
554
        QVector<QPair<QString, QString>> values = StringUtil::parseMailtoUrl(urlDecoded);
555
556
557
558
559
560
561
562
563
        QCOMPARE(values.size(), 3);
        QCOMPARE(values.at(0).first, QLatin1String("to"));
        QCOMPARE(values.at(0).second, QLatin1String("foo@kde.org, bar@kde.org, baz@kde.org"));
        QCOMPARE(values.at(1).first, QLatin1String("cc"));
        QCOMPARE(values.at(1).second, QLatin1String("ss@kde.org"));
        QCOMPARE(values.at(2).first, QLatin1String("bcc"));
        QCOMPARE(values.at(2).second, QLatin1String("ccs@kde.org"));
    }
    {
Laurent Montel's avatar
Laurent Montel committed
564
565
        const QByteArray ba(QByteArrayLiteral("mailto:foo@kde.org?to=bar@kde.org&to=baz@kde.org&cc=ss@kde.org&bcc=ccs@kde.org&to=ff@kde.org"));
        QUrl urlDecoded(QUrl::fromPercentEncoding(ba));
Laurent Montel's avatar
Laurent Montel committed
566
        QVector<QPair<QString, QString>> values = StringUtil::parseMailtoUrl(urlDecoded);
567
568
569
570
571
572
573
574
        QCOMPARE(values.size(), 3);
        QCOMPARE(values.at(0).first, QLatin1String("cc"));
        QCOMPARE(values.at(0).second, QLatin1String("ss@kde.org"));
        QCOMPARE(values.at(1).first, QLatin1String("bcc"));
        QCOMPARE(values.at(1).second, QLatin1String("ccs@kde.org"));
        QCOMPARE(values.at(2).first, QLatin1String("to"));
        QCOMPARE(values.at(2).second, QLatin1String("foo@kde.org, bar@kde.org, baz@kde.org, ff@kde.org"));
    }
575
    {
Laurent Montel's avatar
Laurent Montel committed
576
577
578
579
580
581
        // Bug 427697
        const QByteArray ba(
            QByteArrayLiteral("mailto:julia.lawall%40inria.fr?In-Reply-To=%3Calpine.DEB.2.22.394.2009272255220.20726@hadrien%3E&#38;Cc=Gilles.Muller%40lip6.fr%"
                              "2Ccocci%40systeme.lip6.fr%2Ccorbet%40lwn.net%2Clinux-doc%40vger.kernel.org%2Clinux-kernel%40vger.kernel.org%2Cmichal.lkml%"
                              "40markovi.net%2Cnicolas.palix%40imag.fr%2Csylphrenadin%40gmail.com&#38;Subject=Re%3A%20%5BCocci%5D%20%5BPATCH%201%2F2%5D%"
                              "20scripts%3A%20coccicheck%3A%20Change%20default%20value%20for%09parallelism"));
582
        QUrl urlDecoded(QUrl::fromPercentEncoding(ba));
Laurent Montel's avatar
Laurent Montel committed
583
        QVector<QPair<QString, QString>> values = StringUtil::parseMailtoUrl(urlDecoded);
584
585
586
587
588
589
        QCOMPARE(values.size(), 4);
        QCOMPARE(values.at(0).first, QLatin1String("to"));
        QCOMPARE(values.at(0).second, QLatin1String("julia.lawall@inria.fr"));
        QCOMPARE(values.at(1).first, QLatin1String("in-reply-to"));
        QCOMPARE(values.at(1).second, QLatin1String("<alpine.DEB.2.22.394.2009272255220.20726@hadrien>"));
        QCOMPARE(values.at(2).first, QLatin1String("cc"));
Laurent Montel's avatar
Laurent Montel committed
590
591
592
        QCOMPARE(values.at(2).second,
                 QLatin1String("Gilles.Muller@lip6.fr,cocci@systeme.lip6.fr,corbet@lwn.net,linux-doc@vger.kernel.org,linux-kernel@vger.kernel.org,michal.lkml@"
                               "markovi.net,nicolas.palix@imag.fr,sylphrenadin@gmail.com"));
593
594
595
        QCOMPARE(values.at(3).first, QLatin1String("subject"));
        QCOMPARE(values.at(3).second, QLatin1String("Re: [Cocci] [PATCH 1/2] scripts: coccicheck: Change default value for\tparallelism"));
    }
Laurent Montel's avatar
Laurent Montel committed
596
    {
Laurent Montel's avatar
Laurent Montel committed
597
598
599
600
        // Bug 427697
        const QByteArray ba(
            QByteArrayLiteral("mailto:cocci%40systeme.lip6.fr?Subject=Re%3A%20%5BCocci%5D%20%5BPATCH%5D%20scripts%3A%20coccicheck%3A%20Refactor%20display%"
                              "20messages%20on%0A%20coccinelle%20start%20up&In-Reply-To=%3C20201003142012.idwudlhqiv3a4mjj%40adolin%3E"));
Laurent Montel's avatar
Laurent Montel committed
601
        QUrl urlDecoded(QUrl::fromPercentEncoding(ba));
Laurent Montel's avatar
Laurent Montel committed
602
        QVector<QPair<QString, QString>> values = StringUtil::parseMailtoUrl(urlDecoded);
Laurent Montel's avatar
Laurent Montel committed
603
604
605
606
607
608
609
610
611
        QCOMPARE(values.size(), 3);
        qDebug() << " values " << values;
        QCOMPARE(values.at(0).first, QLatin1String("to"));
        QCOMPARE(values.at(0).second, QLatin1String("cocci@systeme.lip6.fr"));
        QCOMPARE(values.at(1).first, QLatin1String("subject"));
        QCOMPARE(values.at(1).second, QLatin1String("Re: [Cocci] [PATCH] scripts: coccicheck: Refactor display messages on\n coccinelle start up"));
        QCOMPARE(values.at(2).first, QLatin1String("in-reply-to"));
        QCOMPARE(values.at(2).second, QLatin1String("<20201003142012.idwudlhqiv3a4mjj@adolin>"));
    }
612
613
}

614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
void StringUtilTest::test_stripOffMessagePrefix_data()
{
    QTest::addColumn<QString>("subject");
    QTest::addColumn<QString>("result");
    QTest::newRow("no strip needed") << QStringLiteral("Hello World Subject") << QStringLiteral("Hello World Subject");
    QTest::newRow("No default reply forward") << QStringLiteral("AA: Hello World Subject") << QStringLiteral("AA: Hello World Subject");
    QTest::newRow("Default Reply Re:") << QStringLiteral("Re: Hello World Subject") << QStringLiteral("Hello World Subject");
    QTest::newRow("Default Forward FW:") << QStringLiteral("FW: Hello World Subject") << QStringLiteral("Hello World Subject");
    QTest::newRow("Default Forward Fwd:") << QStringLiteral("Fwd: Hello World Subject") << QStringLiteral("Hello World Subject");
    QTest::newRow("Default Reply Re  :") << QStringLiteral("Re  : Hello World Subject") << QStringLiteral("Hello World Subject");
    QTest::newRow("Default Reply Re1:") << QStringLiteral("Re1: Hello World Subject") << QStringLiteral("Hello World Subject");
    QTest::newRow("Default Reply Re[2]:") << QStringLiteral("Re[2]: Hello World Subject") << QStringLiteral("Hello World Subject");
}

void StringUtilTest::test_stripOffMessagePrefix()
{
    QFETCH(QString, subject);
    QFETCH(QString, result);
    const QString subjectAfterStrip = StringUtil::stripOffPrefixes(subject);
    QCOMPARE(subjectAfterStrip, result);
}

636
637
638
639
640
641
642
void StringUtilTest::test_replaceMessagePrefix_data()
{
    QTest::addColumn<QString>("subject");
    QTest::addColumn<bool>("shouldReplace");
    QTest::addColumn<QString>("newPrefix");
    QTest::addColumn<QString>("expectedResult");

Laurent Montel's avatar
Laurent Montel committed
643
    QTest::newRow("no previous prefix") << QStringLiteral("Hello World Subject") << true << QStringLiteral("New_Prefix:")
644
645
                                        << QStringLiteral("New_Prefix: Hello World Subject");

Laurent Montel's avatar
Laurent Montel committed
646
    QTest::newRow("no previous prefix, no replace") << QStringLiteral("Hello World Subject") << false << QStringLiteral("New_Prefix:")
647
648
                                                    << QStringLiteral("New_Prefix: Hello World Subject");

Laurent Montel's avatar
Laurent Montel committed
649
    QTest::newRow("No default reply forward") << QStringLiteral("AA: Hello World Subject") << true << QStringLiteral("New_Prefix:")
650
651
                                              << QStringLiteral("New_Prefix: AA: Hello World Subject");

Laurent Montel's avatar
Laurent Montel committed
652
653
    QTest::newRow("No default reply forward, no replace")
        << QStringLiteral("AA: Hello World Subject") << false << QStringLiteral("New_Prefix:") << QStringLiteral("New_Prefix: AA: Hello World Subject");
654

Laurent Montel's avatar
Laurent Montel committed
655
    QTest::newRow("Default Reply Re:") << QStringLiteral("Re: Hello World Subject") << true << QStringLiteral("New_Prefix:")
656
657
                                       << QStringLiteral("New_Prefix: Hello World Subject");

Laurent Montel's avatar
Laurent Montel committed
658
    QTest::newRow("Default Reply Re:, no replace") << QStringLiteral("Re: Hello World Subject") << false << QStringLiteral("New_Prefix:")
659
660
                                                   << QStringLiteral("Re: Hello World Subject");

Laurent Montel's avatar
Laurent Montel committed
661
    QTest::newRow("Default Reply FW:") << QStringLiteral("FW: Hello World Subject") << true << QStringLiteral("New_Prefix:")
662
663
                                       << QStringLiteral("New_Prefix: Hello World Subject");

Laurent Montel's avatar
Laurent Montel committed
664
    QTest::newRow("Default Reply FW:, no replace") << QStringLiteral("FW: Hello World Subject") << false << QStringLiteral("New_Prefix:")
665
666
                                                   << QStringLiteral("FW: Hello World Subject");

Laurent Montel's avatar
Laurent Montel committed
667
    QTest::newRow("Default Reply FWD:") << QStringLiteral("FWD: Hello World Subject") << true << QStringLiteral("New_Prefix:")
668
669
                                        << QStringLiteral("New_Prefix: Hello World Subject");

Laurent Montel's avatar
Laurent Montel committed
670
    QTest::newRow("Default Reply FWD:, no replace") << QStringLiteral("FWD: Hello World Subject") << false << QStringLiteral("New_Prefix:")
671
672
                                                    << QStringLiteral("FWD: Hello World Subject");

Laurent Montel's avatar
Laurent Montel committed
673
    QTest::newRow("Default Reply Re   :") << QStringLiteral("Re   : Hello World Subject") << true << QStringLiteral("New_Prefix:")
674
675
                                          << QStringLiteral("New_Prefix: Hello World Subject");

Laurent Montel's avatar
Laurent Montel committed
676
    QTest::newRow("Default Reply Re   :, no replace") << QStringLiteral("Re   : Hello World Subject") << false << QStringLiteral("New_Prefix:")
Laurent Montel's avatar
Laurent Montel committed
677
                                                      << QStringLiteral("Re   : Hello World Subject");
678

Laurent Montel's avatar
Laurent Montel committed
679
    QTest::newRow("Default Reply Re1:") << QStringLiteral("Re1: Hello World Subject") << true << QStringLiteral("New_Prefix:")
Laurent Montel's avatar
Laurent Montel committed
680
                                        << QStringLiteral("New_Prefix: Hello World Subject");
681

Laurent Montel's avatar
Laurent Montel committed
682
    QTest::newRow("Default Reply Re1:, no replace") << QStringLiteral("Re1: Hello World Subject") << false << QStringLiteral("New_Prefix:")
683
684
                                                    << QStringLiteral("Re1: Hello World Subject");

Laurent Montel's avatar
Laurent Montel committed
685
    QTest::newRow("Default Reply Re[2]:") << QStringLiteral("Re[2]: Hello World Subject") << true << QStringLiteral("New_Prefix:")
686
687
                                          << QStringLiteral("New_Prefix: Hello World Subject");

Laurent Montel's avatar
Laurent Montel committed
688
    QTest::newRow("Default Reply Re[2]:, no replace") << QStringLiteral("Re[2]: Hello World Subject") << false << QStringLiteral("New_Prefix:")
689
690
691
692
693
694
695
696
697
698
                                                      << QStringLiteral("Re[2]: Hello World Subject");
}

void StringUtilTest::test_replaceMessagePrefix()
{
    QFETCH(QString, subject);
    QFETCH(bool, shouldReplace);
    QFETCH(QString, newPrefix);
    QFETCH(QString, expectedResult);

Laurent Montel's avatar
Laurent Montel committed
699
700
701
702
703
    const QStringList regexPattern = {QStringLiteral("Re\\s*:"),
                                      QStringLiteral("Re\\[\\d+\\]:"),
                                      QStringLiteral("Re\\d+:"),
                                      QStringLiteral("Fwd:"),
                                      QStringLiteral("FW:")};
704
705
706
707
708

    const QString str = StringUtil::replacePrefixes(subject, regexPattern, shouldReplace, newPrefix);
    QCOMPARE(str, expectedResult);
}

709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
void StringUtilTest::test_formatQuotePrefix_data()
{
    QTest::addColumn<QString>("quotePattern");
    QTest::addColumn<QString>("from");
    QTest::addColumn<QString>("result");

    QTest::newRow("empty") << QString() << QString() << QString();
    QTest::newRow("default") << QStringLiteral("> ") << QStringLiteral("Jon Doe") << QStringLiteral("> ");
    QTest::newRow("initials") << QStringLiteral("| %f |") << QStringLiteral("Jon Doe") << QStringLiteral("| JD |");
    QTest::newRow("initials one name") << QStringLiteral("| %f |") << QStringLiteral("Jon") << QStringLiteral("| Jo |");
    QTest::newRow("initials one letter") << QStringLiteral("| %f |") << QStringLiteral("J") << QStringLiteral("| J |");
    QTest::newRow("initials empty name") << QStringLiteral("| %f |") << QString() << QStringLiteral("|  |");
    QTest::newRow("percent") << QStringLiteral("%% %_ %a") << QString() << QStringLiteral("%   %a");
}

void StringUtilTest::test_formatQuotePrefix()
{
    QFETCH(QString, quotePattern);
    QFETCH(QString, from);
    QFETCH(QString, result);
    QCOMPARE(MessageCore::StringUtil::formatQuotePrefix(quotePattern, from), result);
}