1 /*
2     SPDX-FileCopyrightText: 2006 Volker Krause <vkrause@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "contenttest.h"
8 
9 #include <QDebug>
10 #include <QTest>
11 
12 #include <kmime_content.h>
13 #include <kmime_headers.h>
14 #include <kmime_message.h>
15 using namespace KMime;
16 
QTEST_MAIN(ContentTest)17 QTEST_MAIN(ContentTest)
18 
19 void ContentTest::testGetHeaderInstance()
20 {
21     // stuff that looks trivial but breaks if you mess with virtual method signatures (see r534381)
22     auto *myfrom = new Headers::From();
23     QCOMPARE(myfrom->type(), "From");
24     Headers::Base *mybase = myfrom;
25     QCOMPARE(mybase->type(), "From");
26     delete myfrom;
27 
28     // getHeaderInstance() is protected, so we need to test it via KMime::Message
29     auto *c = new Message();
30     Headers::From *f1 = c->from(true);
31     Headers::From *f2 = c->from(true);
32     QCOMPARE(f1, f2);
33     delete c;
34 }
35 
testHeaderAddRemove()36 void ContentTest::testHeaderAddRemove()
37 {
38     // Add a Content-Description header to a content.
39     auto *c = new Content;
40     QVERIFY(!c->contentDescription(false));
41     c->contentDescription()->from7BitString("description");
42 
43     // The content must now have the header.
44     QVERIFY(c->contentDescription(false));
45     QCOMPARE(c->contentDescription()->as7BitString(false), QByteArray("description"));
46 
47     // The content's head must also have the header.  Save the head.
48     c->assemble();
49     QByteArray head = c->head();
50 
51     // Clear the content.  It must now forget the cached header.
52     c->clear();
53     QVERIFY(c->head().isEmpty());
54     QVERIFY(!c->contentDescription(false));
55 
56     // Put the head back.  It must now remember the header.
57     c->setHead(head);
58     QVERIFY(!c->contentDescription(false));
59     c->parse();
60     QVERIFY(c->contentDescription(false));
61     c->contentDescription()->from7BitString("description");
62 
63     // Now remove the header explicitly.
64     bool ret = c->removeHeader("Content-Description");
65     QVERIFY(ret);
66 
67     // The content must have forgotten the header now.
68     QVERIFY(!c->contentDescription(false));
69 
70     // And after assembly the header should stay gone.
71     c->assemble();
72     //QVERIFY(c->head().isEmpty());
73     QVERIFY(!c->contentDescription(false));
74 
75     delete c;
76 
77     // test template versions
78     Content c2;
79     QVERIFY(!c2.hasHeader(KMime::Headers::Lines::staticType()));
80     QVERIFY(!c2.header<KMime::Headers::Lines>(false));
81     QVERIFY(c2.header<KMime::Headers::Lines>(true));
82     QVERIFY(c2.hasHeader(KMime::Headers::Lines::staticType()));
83     c2.removeHeader<KMime::Headers::Lines>();
84     QVERIFY(!c2.hasHeader(KMime::Headers::Lines::staticType()));
85 }
86 
testHeaderAppend()87 void ContentTest::testHeaderAppend()
88 {
89     auto *c = new Content;
90     QByteArray d1("Resent-From: test1@example.com");
91     QByteArray d2("Resent-From: test2@example.com");
92     auto *h1 = new Headers::Generic("Resent-From");
93     h1->from7BitString("test1@example.com");
94     auto *h2 = new Headers::Generic("Resent-From");
95     h2->from7BitString("test2@example.com");
96     c->appendHeader(h1);
97     c->appendHeader(h2);
98     c->assemble();
99     QByteArray head = d1 + '\n' + d2 + '\n';
100     QCOMPARE(c->head(), head);
101 
102     QByteArray d3("Resent-From: test3@example.com");
103     auto *h3 = new Headers::Generic("Resent-From");
104     h3->from7BitString("test3@example.com");
105     c->appendHeader(h3);
106     c->assemble();
107     head.append(d3 + '\n');
108     QCOMPARE(c->head(), head);
109     delete c;
110 }
111 
testImplicitMultipartGeneration()112 void ContentTest::testImplicitMultipartGeneration()
113 {
114     auto *c1 = new Content();
115     c1->contentType()->from7BitString("text/plain");
116     c1->setBody("textpart");
117 
118     auto *c2 = new Content();
119     c2->contentType()->from7BitString("text/html");
120     c2->setBody("htmlpart");
121 
122     c1->addContent(c2);
123 
124     // c1 implicitly converted into a multipart/mixed node.
125     QVERIFY(c1->contentType(false));
126     QCOMPARE(c1->contentType()->mimeType(), QByteArray("multipart/mixed"));
127     QVERIFY(c1->body().isEmpty());
128 
129     QCOMPARE(c1->contents().count(), 2);
130     Content *c = c1->contents().at(0);   // Former c1.
131     QVERIFY(c->contentType(false));
132     QCOMPARE(c->contentType()->mimeType(), QByteArray("text/plain"));
133     QCOMPARE(c->body(), QByteArray("textpart"));
134 
135     QCOMPARE(c1->contents().at(1), c2);
136 
137     // Now remove c2. c1 should be converted back to a text/plain content.
138     c1->removeContent(c2, false);
139     QVERIFY(c1->contents().isEmpty());
140     QVERIFY(c1->contentType(false));
141     QCOMPARE(c1->contentType()->mimeType(), QByteArray("text/plain"));
142     QCOMPARE(c1->body(), QByteArray("textpart"));
143 
144     // c2 should not have been touched.
145     QVERIFY(c2->contents().isEmpty());
146     QVERIFY(c2->contentType(false));
147     QCOMPARE(c2->contentType()->mimeType(), QByteArray("text/html"));
148     QCOMPARE(c2->body(), QByteArray("htmlpart"));
149 
150     // Clean up.
151     delete c1;
152     delete c2;
153 }
154 
testExplicitMultipartGeneration()155 void ContentTest::testExplicitMultipartGeneration()
156 {
157     auto *c1 = new Content();
158     c1->contentType()->from7BitString("multipart/mixed");
159 
160     auto *c2 = new Content();
161     c2->contentType()->from7BitString("text/plain");
162     c2->setBody("textpart");
163 
164     auto *c3 = new Content();
165     c3->contentType()->from7BitString("text/html");
166     c3->setBody("htmlpart");
167 
168     c1->addContent(c2);
169     c1->addContent(c3);
170 
171     // c1 should not have been changed.
172     QCOMPARE(c1->contentType()->mimeType(), QByteArray("multipart/mixed"));
173     QVERIFY(c1->body().isEmpty());
174 
175     QCOMPARE(c1->contents().count(), 2);
176     QCOMPARE(c1->contents().at(0), c2);
177     QCOMPARE(c1->contents().at(1), c3);
178 
179     // Removing c3 should turn c1 into a single-part content containing the data of c2.
180     c1->removeContent(c3, false);
181     QCOMPARE(c1->contentType()->mimeType(), QByteArray("text/plain"));
182     QCOMPARE(c1->contents().count(), 0);
183     QCOMPARE(c1->body(), QByteArray("textpart"));
184 
185     // Clean up.
186     delete c1;
187     // c2 was deleted when c1 turned itself single-part.
188     delete c3;
189 }
190 
testSetContent()191 void ContentTest::testSetContent()
192 {
193     auto *c = new Content();
194     QVERIFY(!c->hasContent());
195 
196     // head and body present
197     c->setContent("head1\nhead2\n\nbody1\n\nbody2\n");
198     QVERIFY(c->hasContent());
199     QCOMPARE(c->head(), QByteArray("head1\nhead2\n"));
200     QCOMPARE(c->body(), QByteArray("body1\n\nbody2\n"));
201 
202     // empty content
203     c->setContent(QByteArray());
204     QVERIFY(!c->hasContent());
205     QVERIFY(c->head().isEmpty());
206     QVERIFY(c->body().isEmpty());
207 
208     // empty head
209     c->setContent("\nbody1\n\nbody2\n");
210     QVERIFY(c->hasContent());
211     QVERIFY(c->head().isEmpty());
212     QCOMPARE(c->body(), QByteArray("body1\n\nbody2\n"));
213 
214     // empty body
215     c->setContent("head1\nhead2\n\n");
216     QVERIFY(c->hasContent());
217     QCOMPARE(c->head(), QByteArray("head1\nhead2\n"));
218     QVERIFY(c->body().isEmpty());
219 
220     delete c;
221 }
222 
testEncodedContent()223 void ContentTest::testEncodedContent()
224 {
225     // Example taken from RFC 2046, section 5.1.1.
226     // Removed "preamble" and "epilogue", which KMime loses.
227     QByteArray data =
228         "From: Nathaniel Borenstein <nsb@bellcore.com>\n"
229         "To: Ned Freed <ned@innosoft.com>\n"
230         "Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)\n"
231         "Subject: Sample message\n"
232         "MIME-Version: 1.0\n"
233         "Content-type: multipart/mixed; boundary=\"simple boundary\"\n"
234         "\n"
235         "\n"
236         "--simple boundary\n"
237         "\n"
238         "This is implicitly typed plain US-ASCII text.\n"
239         "It does NOT end with a linebreak.\n"
240         "--simple boundary\n"
241         "Content-type: text/plain; charset=us-ascii\n"
242         "\n"
243         "This is explicitly typed plain US-ASCII text.\n"
244         "It DOES end with a linebreak.\n"
245         "\n"
246         "--simple boundary--\n";
247 
248     auto *msg = new Message;
249     msg->setContent(data);
250     msg->parse();
251 
252     // Test that multiple calls do not corrupt anything.
253     //QByteArray encc = msg->encodedContent();
254     //qDebug() << "original data" << data;
255     //qDebug() << "encodedContent" << encc;
256     QCOMPARE(msg->encodedContent(), data);
257     QCOMPARE(msg->encodedContent(), data);
258     QCOMPARE(msg->encodedContent(), data);
259     delete msg;
260 
261     // RFC 2822 3.5: lines are limited to 1000 characters (998 + CRLF)
262     // (bug #187345)
263     msg = new Message();
264     data =
265         "Subject:"
266         "test test test test test test test test test test test test test test test test test test test test "
267         "test test test test test test test test test test test test test test test test test test test test "
268         "test test test test test test test test test test test test test test test test test test test test "
269         "test test test test test test test test test test test test test test test test test test test test "
270         "test test test test test test test test test test test test test test test test test test test test "
271         "test test test test test test test test test test test test test test test test test test test test "
272         "test test test test test test test test test test test test test test test test test test test test "
273         "test test test test test test test test test test test test test test test test test test test test "
274         "test test test test test test test test test test test test test test test test test test test test "
275         "test test test test test test test test test test test test test test test test test test test test"
276         "\n"
277         "References: "
278         "<test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> "
279         "<test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> "
280         "<test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> "
281         "<test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> "
282         "<test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> "
283         "<test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> "
284         "<test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> "
285         "<test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> "
286         "<test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> "
287         "<test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com> <test1@example.com>"
288         "\n\n"
289         "body\n";
290     msg->setContent(data);
291     QByteArray content = msg->encodedContent(true /* use CRLF */);
292     const QStringList lines = QString::fromLatin1(content).split(QStringLiteral("\r\n"));
293     for (const QString &line : lines) {
294         QEXPECT_FAIL("", "KMime does not fold lines longer than 998 characters", Continue);
295         QVERIFY(line.length() < 998 && !line.isEmpty() && line != QLatin1String("body"));
296         // The test should be (after the expected failure disappears):
297         //QVERIFY( line.length() < 998 );
298     }
299     delete msg;
300 
301 }
302 
testDecodedContent()303 void ContentTest::testDecodedContent()
304 {
305     auto *c = new Content();
306     c->setBody("\0");
307     QVERIFY(c->decodedContent() == QByteArray());
308     c->setBody(QByteArray());
309     QVERIFY(c->decodedContent() == QByteArray());
310     c->setBody(" ");
311     QVERIFY(c->decodedContent() == QByteArray(" "));
312     delete c;
313 }
314 
testMultipleHeaderExtraction()315 void ContentTest::testMultipleHeaderExtraction()
316 {
317     QByteArray data =
318         "From: Nathaniel Borenstein <nsb@bellcore.com>\n"
319         "To: Ned Freed <ned@innosoft.com>\n"
320         "Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)\n"
321         "Subject: Sample message\n"
322         "Received: from ktown.kde.org ([192.168.100.1])\n"
323         "Received: from dev1.kde.org ([192.168.100.2])\n"
324         "\t by ktown.kde.org ([192.168.100.1])\n"
325         "Received: from dev2.kde.org ([192.168.100.3])\n"
326         "           by ktown.kde.org ([192.168.100.1])\n";
327 
328     auto *msg = new Message();
329     msg->setContent(data);
330     // FAILS identically to ContentTest::testMultipartMixed
331     //  QCOMPARE( msg->encodedContent(), data );
332     msg->parse();
333 
334     auto result = msg->headersByType("Received");
335     QCOMPARE(result.count(), 3);
336     QCOMPARE(result[0]->asUnicodeString(),  QString::fromLatin1("from ktown.kde.org ([192.168.100.1])"));
337     QCOMPARE(result[1]->asUnicodeString(),  QString::fromLatin1("from dev1.kde.org ([192.168.100.2]) by ktown.kde.org ([192.168.100.1])"));
338     QCOMPARE(result[2]->asUnicodeString(),  QString::fromLatin1("from dev2.kde.org ([192.168.100.3]) by ktown.kde.org ([192.168.100.1])"));
339     delete msg;
340 }
341 
testMultipartMixed()342 void ContentTest::testMultipartMixed()
343 {
344     // example taken from RFC 2046, section 5.1.1.
345     QByteArray data =
346         "From: Nathaniel Borenstein <nsb@bellcore.com>\n"
347         "To: Ned Freed <ned@innosoft.com>\n"
348         "Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)\n"
349         "Subject: Sample message\n"
350         "MIME-Version: 1.0\n"
351         "Content-type: multipart/mixed; boundary=\"simple boundary\"\n"
352         "\n"
353         "This is the preamble.  It is to be ignored, though it\n"
354         "is a handy place for composition agents to include an\n"
355         "explanatory note to non-MIME conformant readers.\n"
356         "\n"
357         "--simple boundary\n"
358         "\n"
359         "This is implicitly typed plain US-ASCII text.\n"
360         "It does NOT end with a linebreak.\n"
361         "--simple boundary\n"
362         "Content-type: text/plain; charset=us-ascii\n"
363         "\n"
364         "This is explicitly typed plain US-ASCII text.\n"
365         "It DOES end with a linebreak.\n"
366         "\n"
367         "--simple boundary--\n"
368         "\n"
369         "This is the epilogue.  It is also to be ignored.\n";
370 
371     QByteArray part1 =
372         "This is implicitly typed plain US-ASCII text.\n"
373         "It does NOT end with a linebreak.";
374 
375     QByteArray part2 =
376         "This is explicitly typed plain US-ASCII text.\n"
377         "It DOES end with a linebreak.\n";
378 
379     // What we expect KMime to parse the above data into.
380     QByteArray parsedWithPreambleAndEpilogue =
381         "From: Nathaniel Borenstein <nsb@bellcore.com>\n"
382         "To: Ned Freed <ned@innosoft.com>\n"
383         "Date: Sun, 21 Mar 1993 23:56:48 -0800\n"
384         "Subject: Sample message\n"
385         "MIME-Version: 1.0\n"
386         "Content-Type: multipart/mixed; boundary=\"simple boundary\"\n"
387         "\n"
388         "This is the preamble.  It is to be ignored, though it\n"
389         "is a handy place for composition agents to include an\n"
390         "explanatory note to non-MIME conformant readers.\n"
391         "\n"
392         "--simple boundary\n"
393         "Content-Type: text/plain; charset=\"us-ascii\"\n\n"
394         "This is implicitly typed plain US-ASCII text.\n"
395         "It does NOT end with a linebreak.\n"
396         "--simple boundary\n"
397         "Content-Type: text/plain; charset=\"us-ascii\"\n"
398         "\n"
399         "This is explicitly typed plain US-ASCII text.\n"
400         "It DOES end with a linebreak.\n"
401         "\n"
402         "--simple boundary--\n"
403         "\n"
404         "This is the epilogue.  It is also to be ignored.\n";
405 
406     // What we expect KMime to assemble the above data into.
407     QByteArray assembled =
408         "From: Nathaniel Borenstein <nsb@bellcore.com>\n"
409         "To: Ned Freed <ned@innosoft.com>\n"
410         "Date: Sun, 21 Mar 1993 23:56:48 -0800\n"
411         "Subject: Sample message\n"
412         "MIME-Version: 1.0\n"
413         "Content-Type: multipart/mixed; boundary=\"simple boundary\"\n"
414         "Content-Transfer-Encoding: 7Bit\n"
415         "\n"
416         "--simple boundary\n"
417         "\n"
418         "This is implicitly typed plain US-ASCII text.\n"
419         "It does NOT end with a linebreak.\n"
420         "--simple boundary\n"
421         "Content-Type: text/plain; charset=\"us-ascii\"\n"
422         "\n"
423         "This is explicitly typed plain US-ASCII text.\n"
424         "It DOES end with a linebreak.\n"
425         "\n"
426         "--simple boundary--\n";
427 
428     // test parsing
429     auto *msg = new Message();
430     msg->setContent(data);
431     QCOMPARE(msg->encodedContent(), data);
432     msg->parse();
433     QVERIFY(msg->contentType()->isMultipart());
434 
435     auto list = msg->contents();
436     QCOMPARE(list.count(), 2);
437     Content *c = list.takeFirst();
438     QCOMPARE(c->body(), part1);
439     c = list.takeFirst();
440     QCOMPARE(c->body(), part2);
441 
442     // assemble again
443     msg->assemble();
444     //qDebug() << "expected assembled content" << parsedWithPreambleAndEpilogue;
445     //qDebug() << "actual new encoded content" << msg->encodedContent();
446     QCOMPARE(msg->encodedContent(), parsedWithPreambleAndEpilogue);
447     delete msg;
448 
449     // assembling from scratch
450     // (The headers have to be in the same order, as we compare with the above assembled.)
451     msg = new Message();
452     msg->from()->from7BitString("Nathaniel Borenstein <nsb@bellcore.com>");
453     msg->to()->from7BitString("Ned Freed <ned@innosoft.com>");
454     msg->date()->from7BitString("Sun, 21 Mar 1993 23:56:48 -0800 (PST)");
455     msg->subject()->from7BitString("Sample message");
456     // HACK to make MIME-Version appear before Content-Type, as in the expected message.
457     auto header = new Headers::MIMEVersion;
458     header->from7BitString("1.234");
459     msg->setHeader(header);
460     msg->setBody(part1);
461     c = new Content();
462     c->setBody(part2);
463     c->contentType()->setMimeType("text/plain");
464     c->contentType()->setCharset("us-ascii");
465     msg->addContent(c);
466     msg->contentType()->setBoundary("simple boundary");
467 
468     list = msg->contents();
469     QCOMPARE(list.count(), 2);
470     c = list.takeFirst();
471     QCOMPARE(c->body(), part1);
472     c = list.takeFirst();
473     QCOMPARE(c->body(), part2);
474 
475     msg->assemble();
476     //QByteArray encc = msg->encodedContent();
477     //qDebug() << "expected assembled content" << assembled;
478     //qDebug() << "actual encoded content" << encc;
479     QCOMPARE(msg->encodedContent(), assembled);
480     delete msg;
481 }
482 
testParsingUuencoded()483 void ContentTest::testParsingUuencoded()
484 {
485     const QByteArray body =
486         "This is a test message that should appears as a text/plain part\n"
487         "once this message is parsed and convert to a MIME tree.\n"
488         "\n"
489         "\n";
490 
491     const QString imageName = QStringLiteral("Name of the encoded file (oxygen 22x22 kde.png)");
492     const QByteArray imageBase64 =
493         "\n"
494         "iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n"
495         "AAADdgAAA3YBfdWCzAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAU4SURB\n"
496         "VBgZjcFbiF1XGcDx/7fW2pdznTOXNJlJc4WWVK3RqiC2FOyTiGCs+lKkohWTIl5QWrAp9ckLKpYi\n"
497         "KFjwodgXoRDMk1VECj7UpkIqUZuCTUycyWXOzJxz9tnXtdfnxNqnvvj7iaryNhGxgPBOAh/gLa+y\n"
498         "S3kn3dXyP6KqyEe+1Rm6tSc6nYVHO+loOXYR1hisFYRAIOBljtecyPaItEMkXeK4S2QTVAxVOZ1t\n"
499         "TzaeG6//9fTWuR9MnOxyx7/xzaWjB548cvgAUeyJbGDYj9mzPGJl1GdpocOwlxCCMs1qtrKSrZ2c\n"
500         "ze0Z126O2ZkWSJoO0rDylUabREROOsCoG3z58JEDrK4NIFQMBz0WBl2G3ZReGiNi+debO6gKC3sH\n"
501         "DAcxNu6QpF1GiwtsTzMm04wrVyeY7upngEcdYIy4pSgVJtmMYb+HmBiVGE9Eo47ZdsHJj3eJnOHp\n"
502         "M3P6exbIJxmffr/ibMK58zN+M4nwlGCTPmAMu8QYKasCFYd1CWoSgkT4YGmCoWggTRLiOKH0UFTK\n"
503         "A8csdx0ZcnBfl/PXIuJ+j253gBED3CEGDluxVtqgGBcTJCKIZboxJq9bssozLxqiKMJZS1G3LIct\n"
504         "7nvfAs5FvPDSjHlnEbER3f4AsUZYG1rD2t3GGIu4GIhosUSzCd9/5HZOvKtldnmd7evbRM7hnEOz\n"
505         "CV/8xCrOWv52qeKVGx0CBpUIF3cwxsLwdmtYPGSMtaLW0WIIKuStIXLCh9+9wE++fgfWV4jwX489\n"
506         "fJQkMswr5ee/26EMgaaFVoW6VsRaGXWWrFnqWyPWSV0rrULlA7M45dd/uEHZwOlfvMGW6yAiiAhr\n"
507         "KwkgGIEiL8jrmryuqWpPWbWItYTlNWvauGeNs8xLT9W2FFXDdGPMwb0pz569wsUqpqgbQADhmecu\n"
508         "IgK91HHqY7cxz0um85zxrKAVMNYSbGKNtqkIhtB6xptTvntiyJnv3MVH71niT3+fUHvQ1vC2F1+v\n"
509         "efHPm9xy33sXubtXsj3NaJqKNjSA0KePEVsqKEE9dZWTOBCUtg1sZoamhrYFVQWUphV+dPYml67l\n"
510         "3PLtz99Jr8zxdYn3NSJKRoYxhQZ2+aZCteWhZy7yydOvceHNOXeuWNRbQmMIIaCqGGJcOuL0s5fJ\n"
511         "S8+gY3j8U4fQ2hPqEg0BqQnCsUcGg7XjNxZXV1MbJQx6I1ZW9vPge4QHPrjM47/cwXZ6VFmBaEsy\n"
512         "6GPqgqEtqJqWsmq4OpmT+Sl1XTHdHIemeG3ZML3RBu+1rkp8mROahqiYceL+RQ7eZvnewwusyoRh\n"
513         "f8hgtMywmfPUQ0Oe+sI+WlJ0tIrrJjR1SdMUBO/Z2fhn61g/68PRe7UqC4JraDo1h3oVsW1440rD\n"
514         "718uOfXgiL1LEIKiOsI5IY0CT36uzxO/KvF1TV3MqX1D8F6Z/8U7QEPr1WCpyzlVVXJuo+WrP7xE\n"
515         "ke5neeUA55+/ytNfSxAnPPazEnVdPntvweV/52R5oK4KqiqnqhtQr1y50jpAQ1PmvbTfG493mE62\n"
516         "oYV/+CWGgzFN8EQm5vo4RxWmLKBty09/65nPC6bZDjuTLeZZhrMJWs8rdjkghOmlF3x57NTy4hrX\n"
517         "b65T5zl1WVAWc7LuhDTpcvLHFcYY6E7xTUNZ5eT5jFm2w3S6RWRT9oz2cXX9lT8Cragqsv9DK93F\n"
518         "48/3995zf7e/J41dhDMWawQkoNriTYbXnMj2ibRLJF3iuEtkE1SEfL7VXLv00qs3Xz/zpWp84YKo\n"
519         "KreIiANGwH5AAOH/o7xlE7gOeN31H1IDp2dl3tAoAAAAAElFTkSuQmCC\n"
520         ;
521 
522     const QByteArray uuencodedMsg =
523         "Path: news.example.net!not-for-mail\n"
524         "From: Coin coin <meuh@example.net>\n"
525         "Newsgroups: test.kmime.uuencoded\n"
526         "Subject: Kmime test\n"
527         "Date: Thu, 14 Apr 2005 20:12:47 -0700\n"
528         "Message-ID: <xxxxxxxxxxxxxxxxxx@xxxxx.kmime.example.net>\n"
529         "X-Newsreader: Forte Agent 2.0/32.640\n"
530         "Lines: 1283\n"
531         "Organization: Ament\n"
532         "Xref: news.example.net test.kmime.uuencoded:4584\n"
533         "\n"
534         "This is a test message that should appears as a text/plain part\n"
535         "once this message is parsed and convert to a MIME tree.\n"
536         "\n"
537         "begin 644 Name of the encoded file (oxygen 22x22 kde.png)\n"
538         "MB5!.1PT*&@H````-24A$4@```!8````6\"`8```#$M&P[````!'-\"250(\"`@(\n"
539         "M?`ADB`````EP2%ES```#=@```W8!?=6\"S````!ET15AT4V]F='=A<F4`=W=W\n"
540         "M+FEN:W-C87!E+F]R9YON/!H```4X241!5!@9C<%;B%U7&<#Q_[?6VI=SG3.7\n"
541         "M-)E)<X665*W1JB\"V%.R3B&\"L^E*DHA63(EY06K`I]<D+*I8B*%CPH=@7H1#,\n"
542         "MDU5$\"C[4ID(J49N\"34R<R67.S)QS]MG7M=?GQ-JGOOC[B:KR-A&Q@/!.`A_@\n"
543         "M+:^R2WDGW=7R/Z*JR$>^U1FZM2<ZG85'.^EH.781UABL%81`(.!ECM><R/:(\n"
544         "MM$,D7>*X2V035`Q5.9UM3S:>&Z__]?36N1],G.QRQ[_QS:6C!YX\\<O@`4>R)\n"
545         "M;&#8C]FS/&)EU&=IH<.PEQ\"\",LUJMK*2K9V<S>T9UVZ.V9D62)H.TK#RE4:;\n"
546         "M1$1..L\"H&WSY\\)$#K*X-(%0,!ST6!EV&W91>&B-B^=>;.Z@*\"WL'#`<Q-NZ0\n"
547         "MI%U&BPML3S,FTXPK5R>8[NIG@$<=8(RXI2@5)MF,8;^'F!B5&$]$HX[9=L')\n"
548         "MCW>)G.'I,W/Z>Q;()QF??K_B;,*Y\\S-^,XGPE&\"3/F`,N\\08*:L\"%8=U\"6H2\n"
549         "M@D3X8&F\"H6@@31+B.*'T4%3*`\\<L=QT9<G!?E_/7(N)^CVYW@!$#W\"$&#ENQ\n"
550         "M5MJ@&!<3)\"*(9;HQ)J];LLHS+QJB*,)92U&W+(<M[GO?`LY%O/#2C'EG$;$1\n"
551         "MW?X`L498&UK#VMW&&(NX&(AHL42S\"=]_Y'9.O*ME=GF=[>O;1,[AG$.S\"5_\\\n"
552         "MQ\"K.6OYVJ>*5&QT\"!I4(%W<PQL+P=FM8/&2,M:+6T6((*N2M(7+\"A]^]P$^^\n"
553         "M?@?65XCP7X\\]?)0D,LPKY>>_VZ$,@::%5H6Z5L1:&766K%GJ6R/625TKK4+E\n"
554         "M`[,XY==_N$'9P.E?O,&6ZR`BB`AK*PD@&($B+\\CKFKRNJ6I/6;6(M83E-6O:\n"
555         "MN&>-L\\Q+3]6V%%7#=&/,P;TISYZ]PL4JIJ@;0`#AF><N(@*]U''J8[<QSTNF\n"
556         "M\\YSQK*`5,-82;&*-MJD(AM!ZQIM3OGMBR)GOW,5'[UGB3W^?4'O0UO\"V%U^O\n"
557         "M>?'/F]QRWWL7N;M7LCW-:)J*-C2`T*>/$5LJ*$$]=963.!\"4M@UL9H:FAK8%\n"
558         "M5064IA5^=/8FEZ[EW/+MS]])K\\SQ=8GW-2)*1H8QA09V^:9\"M>6A9R[RR=.O\n"
559         "M<>'-.7>N6-1;0F,((:\"J&&)<.N+TLY?)2\\^@8WC\\4X?0VA/J$@T!J0G\"L4<&\n"
560         "M@[7C-Q975U,;)0QZ(U96]O/@>X0'/KC,X[_<P79Z5%F!:$LRZ&/J@J$MJ)J6\n"
561         "MLFJX.IF3^2EU73'='(>F>&W9,+W1!N^UKDI\\F1.:AJB8<>+^10[>9OG>PPNL\n"
562         "MRH1A?\\A@M,RPF?/40T.>^L(^6E)TM(KK)C1U2=,4!._9V?AGZU@_Z\\/1>[4J\n"
563         "M\"X)K:#HUAWH5L6UXXTK#[U\\N.?7@B+U+$(*B.L(Y(8T\"3WZNSQ._*O%U35W,\n"
564         "MJ7U#\\%Z9_\\4[0$/KU6\"IRSE557)NH^6K/[Q$D>YG>>4`YY^_RM-?2Q`G//:S\n"
565         "M$G5=/GMOP>5_YV1YH*X*JBJGJAM0KURYTCI`0U/FO;3?&X]WF$ZVH85_^\"6&\n"
566         "M@S%-\\$0FYOHX1Q6F+*!MRT]_ZYG/\"Z;9#CN3+>99AK,)6L\\K=CD@A.FE%WQY\n"
567         "M[-3RXAK7;ZY3YSEU65`6<[+NA#3I<O+'%<88Z$[Q34-9Y>3YC%FVPW2Z1613\n"
568         "M]HSV<77]E3\\\"K:@JLO]#*]W%X\\_W]]YS?[>_)XU=A#,6:P0DH-KB38;7G,CV\n"
569         "MB;1+)%WBN$MD$U2$?+[57+OTTJLW7S_SI6I\\X8*H*K>(B`-&P'Y``.'_H[QE\n"
570         ";$[@.>-WU'U(#IV=EWM`H`````$E%3D2N0F\"\"\n"
571         "`\n"
572         "end\n"
573         "\n";
574 
575     auto *msg = new Message();
576     msg->setContent(uuencodedMsg);
577     msg->parse();
578     auto contents = msg->contents();
579 
580     // text + image
581     QCOMPARE(contents.size(), 2);
582 
583     Content *c = nullptr;
584 
585     // Check the first text part
586     c = contents.at(0);
587     QVERIFY(c->contentType()->isPlainText());
588     QCOMPARE(c->body(), body);
589 
590     // Check the image part
591     c = contents.at(1);
592     QVERIFY(!c->contentType()->isText());
593     QCOMPARE(c->contentType()->name(), imageName);
594     // The uuencoded content as been recoded as base64
595     QCOMPARE(c->encodedContent().data(), imageBase64.data());
596 
597     delete msg;
598 }
599 
testParent()600 void ContentTest::testParent()
601 {
602     auto *c1 = new Content();
603     c1->contentType()->from7BitString("multipart/mixed");
604 
605     auto *c2 = new Content();
606     c2->contentType()->from7BitString("text/plain");
607     c2->setBody("textpart");
608 
609     auto *c3 = new Content();
610     c3->contentType()->from7BitString("text/html");
611     c3->setBody("htmlpart");
612 
613     auto *c4 = new Content();
614     c4->contentType()->from7BitString("text/html");
615     c4->setBody("htmlpart2");
616 
617     auto *c5 = new Content();
618     c5->contentType()->from7BitString("multipart/mixed");
619 
620     //c2 doesn't have a parent yet
621     QCOMPARE(c2->parent(), (Content *)nullptr);
622 
623     c1->addContent(c2);
624     c1->addContent(c3);
625     c1->addContent(c4);
626 
627     // c1 is the parent of those
628     QCOMPARE(c2->parent(), c1);
629     QCOMPARE(c3->parent(), c1);
630 
631     //test removal
632     c1->removeContent(c2, false);
633     QCOMPARE(c2->parent(), (Content *)nullptr);
634     QCOMPARE(c1->contents().at(0), c3);
635 
636 //check if the content is moved correctly to another parent
637     c5->addContent(c4);
638     QCOMPARE(c4->parent(), c5);
639     QCOMPARE(c1->contents().count(), 0);   //yes, it should be 0
640     QCOMPARE(c5->contents().at(0), c4);
641 
642     // example taken from RFC 2046, section 5.1.1.
643     QByteArray data =
644         "From: Nathaniel Borenstein <nsb@bellcore.com>\n"
645         "To: Ned Freed <ned@innosoft.com>\n"
646         "Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)\n"
647         "Subject: Sample message\n"
648         "MIME-Version: 1.0\n"
649         "Content-type: multipart/mixed; boundary=\"simple boundary\"\n"
650         "\n"
651         "This is the preamble.  It is to be ignored, though it\n"
652         "is a handy place for composition agents to include an\n"
653         "explanatory note to non-MIME conformant readers.\n"
654         "\n"
655         "--simple boundary\n"
656         "\n"
657         "This is implicitly typed plain US-ASCII text.\n"
658         "It does NOT end with a linebreak.\n"
659         "--simple boundary\n"
660         "Content-type: text/plain; charset=us-ascii\n"
661         "\n"
662         "This is explicitly typed plain US-ASCII text.\n"
663         "It DOES end with a linebreak.\n"
664         "\n"
665         "--simple boundary--\n"
666         "\n"
667         "This is the epilogue.  It is also to be ignored.\n";
668 
669     // test parsing
670     auto *msg = new Message();
671     msg->setContent(data);
672     msg->parse();
673     QCOMPARE(msg->parent(), (Content *)nullptr);
674     QCOMPARE(msg->contents().at(0)->parent(), msg);
675     QCOMPARE(msg->contents().at(1)->parent(), msg);
676     delete msg;
677 
678     delete c1;
679     delete c2;
680     delete c5;
681 }
682 
testFreezing()683 void ContentTest::testFreezing()
684 {
685     // Example taken from RFC 2046, section 5.1.1.
686     QByteArray data =
687         "From: Nathaniel Borenstein <nsb@bellcore.com>\n"
688         "To: Ned Freed <ned@innosoft.com>\n"
689         "Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)\n"
690         "Subject: Sample message\n"
691         "MIME-Version: 1.0\n"
692         "Content-type: multipart/mixed; boundary=\"simple boundary\"\n"
693         "\n"
694         "This is the preamble.  It is to be ignored, though it\n"
695         "is a handy place for composition agents to include an\n"
696         "explanatory note to non-MIME conformant readers.\n"
697         "\n"
698         "--simple boundary\n"
699         "\n"
700         "This is implicitly typed plain US-ASCII text.\n"
701         "It does NOT end with a linebreak.\n"
702         "--simple boundary\n"
703         "Content-type: text/plain; charset=us-ascii\n"
704         "\n"
705         "This is explicitly typed plain US-ASCII text.\n"
706         "It DOES end with a linebreak.\n"
707         "\n"
708         "--simple boundary--\n"
709         "\n"
710         "This is the epilogue.  It is also to be ignored.\n";
711 
712     auto *msg = new Message;
713     msg->setContent(data);
714     msg->setFrozen(true);
715 
716     // The data should be untouched before parsing.
717     //qDebug() << "original data" << data;
718     //qDebug() << "data from message" << msg->encodedContent();
719     QCOMPARE(msg->encodedContent(), data);
720 
721     // The data should remain untouched after parsing.
722     msg->parse();
723     QVERIFY(msg->contentType()->isMultipart());
724     QCOMPARE(msg->contents().count(), 2);
725     QCOMPARE(msg->encodedContent(), data);
726 
727     // Calling assemble() should not alter the data.
728     msg->assemble();
729     QCOMPARE(msg->encodedContent(), data);
730     delete msg;
731 }
732 
testContentTypeMimetype_data()733 void ContentTest::testContentTypeMimetype_data()
734 {
735     QTest::addColumn<QByteArray>("data");
736     QTest::addColumn<QByteArray>("mimetype");
737     QTest::addColumn<int>("contentCount");
738     QTest::addColumn<QList<QByteArray> >("contentMimeType");
739     QByteArray data =
740         "From: Nathaniel Borenstein <nsb@bellcore.com>\n"
741         "To: Ned Freed <ned@innosoft.com>\n"
742         "Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)\n"
743         "Subject: Sample message\n"
744         "MIME-Version: 1.0\n"
745         "Content-type: multipart/mixed; boundary=\"simple boundary\"\n"
746         "\n"
747         "This is the preamble.  It is to be ignored, though it\n"
748         "is a handy place for composition agents to include an\n"
749         "explanatory note to non-MIME conformant readers.\n"
750         "\n"
751         "--simple boundary\n"
752         "\n"
753         "This is implicitly typed plain US-ASCII text.\n"
754         "It does NOT end with a linebreak.\n"
755         "--simple boundary\n"
756         "Content-type: text/plain; charset=us-ascii\n"
757         "\n"
758         "This is explicitly typed plain US-ASCII text.\n"
759         "It DOES end with a linebreak.\n"
760         "\n"
761         "--simple boundary--\n"
762         "\n"
763         "This is the epilogue.  It is also to be ignored.\n";
764 
765     QList<QByteArray> contentMimes = { "text/plain", "text/plain"};
766     QTest::newRow("multipart") << data << QByteArrayLiteral("multipart/mixed") << 2 << contentMimes;
767 
768     data =
769             "From: Montel Laurent <null@kde.org>\n"
770             "To: kde-commits@kde.org\n"
771             "Content-Type: text/plain; charset=\"utf-8\"\n"
772             "MIME-Version: 1.0\n"
773             "Content-Transfer-Encoding: quoted-printable\n"
774             "Subject: [libksieve] src/ksieveui: coding style\n"
775             "Date: Tue, 30 May 2017 19:25:59 +0000\n"
776             "\n"
777             "Git commit 5410a2b3b6eef29ba32d0ac5e363fd38a56f535b by Montel Laurent.\n"
778             "Committed on 30/05/2017 at 19:25.\n";
779     QTest::newRow("text/plain") << data << QByteArrayLiteral("text/plain") << 0 << QList<QByteArray>();
780 
781     data =
782             "From: Montel Laurent <null@kde.org>\n"
783             "To: kde-commits@kde.org\n"
784             "Content-Type: %22%22%22%22%22%22%22%22%22%22%22%22%22%22%22%22=?windows-1252?q?ap%22/pdf;\n"
785             "name=\"file.pdf\"\n"
786             "MIME-Version: 1.0\n"
787             "Content-Transfer-Encoding: quoted-printable\n"
788             "Subject: [libksieve] src/ksieveui: coding style\n"
789             "Date: Tue, 30 May 2017 19:25:59 +0000\n"
790             "\n"
791             "Git commit 5410a2b3b6eef29ba32d0ac5e363fd38a56f535b by Montel Laurent.\n"
792             "Committed on 30/05/2017 at 19:25.\n";
793     QTest::newRow("broken") << data << QByteArrayLiteral("text/plain") << 0 << QList<QByteArray>();
794 
795     data =
796             "From: Montel Laurent <null@kde.org>\n"
797             "To: kde-commits@kde.org\n"
798             "Content-Type: application/pdf;\n"
799             "name=\"file.pdf\"\n"
800             "MIME-Version: 1.0\n"
801             "Content-Transfer-Encoding: quoted-printable\n"
802             "Subject: [libksieve] src/ksieveui: coding style\n"
803             "Date: Tue, 30 May 2017 19:25:59 +0000\n"
804             "\n"
805             "Git commit 5410a2b3b6eef29ba32d0ac5e363fd38a56f535b by Montel Laurent.\n"
806             "Committed on 30/05/2017 at 19:25.\n";
807     QTest::newRow("not broken") << data << QByteArrayLiteral("application/pdf") << 0 << QList<QByteArray>();
808 
809     data =
810             "From kde@kde.org Fri Jan 29 19:19:26 2010\n"
811             "Return-Path: <kde@kde.org>\n"
812             "Delivered-To: kde@kde.org\n"
813             "Received: from localhost (localhost [127.0.0.1])\n"
814             "        by mail.kde.org (Postfix) with ESMTP id 8A6FF2B23D\n"
815             "        for <kde@kde.org>; Fri, 29 Jan 2010 19:19:27 +0100 (CET)\n"
816             "From: KDE <kde@kde.org>\n"
817             "To: kde@kde.org\n"
818             "Subject: Attachment test Outlook\n"
819             "Date: Fri, 29 Jan 2010 19:19:26 +0100\n"
820             "User-Agent: KMail/1.13.0 (Linux/2.6.32-gentoo-r1; KDE/4.3.95; x86_64; ; )\n"
821             "MIME-Version: 1.0\n"
822             "Content-Type: Multipart/Mixed;\n"
823             "  boundary=\"Boundary-00=_uayYLfn6pjD7iFW\"\n"
824             "Message-Id: <201001291919.26518.kde@kde.org>\n"
825             "X-Length: 10467\n"
826             "X-UID: 17\n"
827             "\n"
828             "--Boundary-00=_uayYLfn6pjD7iFW\n"
829             "Content-Type: Text/Plain;\n"
830             "  charset=\"us-ascii\"\n"
831             "Content-Transfer-Encoding: 7bit\n"
832             "\n"
833             "Attachment test Outlook\n"
834             "\n"
835             "--Boundary-00=_uayYLfn6pjD7iFW\n"
836             "Content-Type: text/x-patch;\n"
837             "  charset=\"UTF-8\";\n"
838             "  name*=UTF-8''%C3%A5%2Ediff\n"
839             "Content-Transfer-Encoding: 7bit\n"
840             "Content-Disposition: attachment;\n"
841             "  filename=\"=?iso-8859-1?q?=E5=2Ediff?=\"\n"
842             "\n"
843             "Index: kmime/kmime_headers_p.h\n"
844             "===================================================================\n"
845             "--- kmime/kmime_headers_p.h     (revision 1068282)\n"
846             "+++ kmime/kmime_headers_p.h     (working copy)\n"
847             "@@ -170,6 +170,7 @@\n"
848             "     int lines;\n"
849             " };\n"
850             "\n"
851             "+kmime_mk_empty_private( ContentID, Generics::SingleIdent )\n"
852             " }\n"
853             "\n"
854             " }\n"
855             "\n"
856             "--Boundary-00=_uayYLfn6pjD7iFW--";
857     contentMimes = { "text/plain", "text/x-patch"};
858     QTest::newRow("example1") << data << QByteArrayLiteral("multipart/mixed") << 2 << contentMimes;
859 }
860 
861 
testContentTypeMimetype()862 void ContentTest::testContentTypeMimetype()
863 {
864     QFETCH(QByteArray, data);
865     QFETCH(QByteArray, mimetype);
866     QFETCH(int, contentCount);
867     QFETCH(QList<QByteArray> , contentMimeType);
868 
869     // test parsing
870     Message msg;
871     msg.setContent(data);
872     msg.parse();
873     //QEXPECT_FAIL("broken", "Problem with content type", Continue);
874     QCOMPARE(msg.contentType(false)->mimeType(), mimetype);
875     QCOMPARE(msg.contents().count(), contentCount);
876     for (int i = 0; i < msg.contents().count(); ++i) {
877         QVERIFY(msg.contents().at(i)->contentType(false));
878         QCOMPARE(contentMimeType.count(), contentCount);
879         QCOMPARE(msg.contents().at(i)->contentType(false)->mimeType(), contentMimeType.at(i));
880     }
881 }
882