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;C]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