summaryrefslogtreecommitdiffstats
path: root/framework/domain/mailtemplates.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'framework/domain/mailtemplates.cpp')
-rw-r--r--framework/domain/mailtemplates.cpp804
1 files changed, 804 insertions, 0 deletions
diff --git a/framework/domain/mailtemplates.cpp b/framework/domain/mailtemplates.cpp
new file mode 100644
index 00000000..e5ee8533
--- /dev/null
+++ b/framework/domain/mailtemplates.cpp
@@ -0,0 +1,804 @@
1/*
2 Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
3 Copyright (c) 2010 Leo Franchi <lfranchi@kde.org>
4 Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com>
5
6 This library is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Library General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or (at your
9 option) any later version.
10
11 This library is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
14 License for more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301, USA.
20*/
21#include "mailtemplates.h"
22
23#include <QByteArray>
24#include <QList>
25#include <QDebug>
26#include <QImage>
27#include <QWebPage>
28#include <QWebFrame>
29#include <QSysInfo>
30#include <QTextCodec>
31#include <QApplication>
32
33#include <KCodecs/KCharsets>
34#include <KMime/Types>
35
36#include "stringhtmlwriter.h"
37#include "objecttreesource.h"
38#include "csshelper.h"
39
40#include <MessageViewer/ObjectTreeParser>
41
42namespace KMime {
43 namespace Types {
44static bool operator==(const KMime::Types::AddrSpec &left, const KMime::Types::AddrSpec &right)
45{
46 return (left.asString() == right.asString());
47}
48
49static bool operator==(const KMime::Types::Mailbox &left, const KMime::Types::Mailbox &right)
50{
51 return (left.addrSpec().asString() == right.addrSpec().asString());
52}
53 }
54}
55
56static KMime::Types::Mailbox::List stripMyAddressesFromAddressList(const KMime::Types::Mailbox::List &list, const KMime::Types::AddrSpecList me)
57{
58 KMime::Types::Mailbox::List addresses(list);
59 for (KMime::Types::Mailbox::List::Iterator it = addresses.begin(); it != addresses.end();) {
60 if (me.contains(it->addrSpec())) {
61 it = addresses.erase(it);
62 } else {
63 ++it;
64 }
65 }
66
67 return addresses;
68}
69
70void initHeader(const KMime::Message::Ptr &message)
71{
72 message->removeHeader<KMime::Headers::To>();
73 message->removeHeader<KMime::Headers::Subject>();
74 message->date()->setDateTime(QDateTime::currentDateTime());
75
76 const QStringList extraInfo = QStringList() << QSysInfo::prettyProductName();
77 message->userAgent()->fromUnicodeString(QString("%1/%2(%3)").arg(QString::fromLocal8Bit("Kube")).arg("0.1").arg(extraInfo.join(",")), "utf-8");
78 // This will allow to change Content-Type:
79 message->contentType()->setMimeType("text/plain");
80}
81
82QString replacePrefixes(const QString &str, const QStringList &prefixRegExps,
83 bool replace, const QString &newPrefix)
84{
85 bool recognized = false;
86 // construct a big regexp that
87 // 1. is anchored to the beginning of str (sans whitespace)
88 // 2. matches at least one of the part regexps in prefixRegExps
89 QString bigRegExp = QStringLiteral("^(?:\\s+|(?:%1))+\\s*").arg(prefixRegExps.join(QStringLiteral(")|(?:")));
90 QRegExp rx(bigRegExp, Qt::CaseInsensitive);
91 if (rx.isValid()) {
92 QString tmp = str;
93 if (rx.indexIn(tmp) == 0) {
94 recognized = true;
95 if (replace) {
96 return tmp.replace(0, rx.matchedLength(), newPrefix + QLatin1String(" "));
97 }
98 }
99 } else {
100 qWarning() << "bigRegExp = \""
101 << bigRegExp << "\"\n"
102 << "prefix regexp is invalid!";
103 // try good ole Re/Fwd:
104 recognized = str.startsWith(newPrefix);
105 }
106
107 if (!recognized) {
108 return newPrefix + QLatin1String(" ") + str;
109 } else {
110 return str;
111 }
112}
113
114QString cleanSubject(const KMime::Message::Ptr &msg, const QStringList &prefixRegExps, bool replace, const QString &newPrefix)
115{
116 return replacePrefixes(msg->subject()->asUnicodeString(), prefixRegExps, replace, newPrefix);
117}
118
119QString forwardSubject(const KMime::Message::Ptr &msg)
120{
121 bool replaceForwardPrefix = true;
122 QStringList forwardPrefixes;
123 forwardPrefixes << "Fwd:";
124 forwardPrefixes << "FW:";
125 return cleanSubject(msg, forwardPrefixes, replaceForwardPrefix, QStringLiteral("Fwd:"));
126}
127
128QString replySubject(const KMime::Message::Ptr &msg)
129{
130 bool replaceReplyPrefix = true;
131 QStringList replyPrefixes;
132 //We're escaping the regex escape sequences. awesome
133 replyPrefixes << "Re\\\\s*:";
134 replyPrefixes << "Re[\\\\d+\\\\]:";
135 replyPrefixes << "Re\\\\d+:";
136 return cleanSubject(msg, replyPrefixes, replaceReplyPrefix, QStringLiteral("Re:"));
137}
138
139QByteArray getRefStr(const KMime::Message::Ptr &msg)
140{
141 QByteArray firstRef, lastRef, refStr, retRefStr;
142 int i, j;
143
144 if (auto hdr = msg->references(false)) {
145 refStr = hdr->as7BitString(false).trimmed();
146 }
147
148 if (refStr.isEmpty()) {
149 return msg->messageID()->as7BitString(false);
150 }
151
152 i = refStr.indexOf('<');
153 j = refStr.indexOf('>');
154 firstRef = refStr.mid(i, j - i + 1);
155 if (!firstRef.isEmpty()) {
156 retRefStr = firstRef + ' ';
157 }
158
159 i = refStr.lastIndexOf('<');
160 j = refStr.lastIndexOf('>');
161
162 lastRef = refStr.mid(i, j - i + 1);
163 if (!lastRef.isEmpty() && lastRef != firstRef) {
164 retRefStr += lastRef + ' ';
165 }
166
167 retRefStr += msg->messageID()->as7BitString(false);
168 return retRefStr;
169}
170
171KMime::Content *createPlainPartContent(const KMime::Message::Ptr &msg, const QString &plainBody)
172{
173 KMime::Content *textPart = new KMime::Content(msg.data());
174 textPart->contentType()->setMimeType("text/plain");
175 //FIXME This is supposed to select a charset out of the available charsets that contains all necessary characters to render the text
176 // QTextCodec *charset = selectCharset(m_charsets, plainBody);
177 // textPart->contentType()->setCharset(charset->name());
178 textPart->contentType()->setCharset("utf-8");
179 textPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE8Bit);
180 textPart->fromUnicodeString(plainBody);
181 return textPart;
182}
183
184KMime::Content *createMultipartAlternativeContent(const KMime::Message::Ptr &msg, const QString &plainBody, const QString &htmlBody)
185{
186 KMime::Content *multipartAlternative = new KMime::Content(msg.data());
187 multipartAlternative->contentType()->setMimeType("multipart/alternative");
188 const QByteArray boundary = KMime::multiPartBoundary();
189 multipartAlternative->contentType()->setBoundary(boundary);
190
191 KMime::Content *textPart = createPlainPartContent(msg, plainBody);
192 multipartAlternative->addContent(textPart);
193
194 KMime::Content *htmlPart = new KMime::Content(msg.data());
195 htmlPart->contentType()->setMimeType("text/html");
196 //FIXME This is supposed to select a charset out of the available charsets that contains all necessary characters to render the text
197 // QTextCodec *charset = selectCharset(m_charsets, htmlBody);
198 // htmlPart->contentType()->setCharset(charset->name());
199 textPart->contentType()->setCharset("utf-8");
200 htmlPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE8Bit);
201 htmlPart->fromUnicodeString(htmlBody);
202 multipartAlternative->addContent(htmlPart);
203
204 return multipartAlternative;
205}
206
207void addProcessedBodyToMessage(const KMime::Message::Ptr &msg, const QString &plainBody, const QString &htmlBody, bool forward)
208{
209 //FIXME
210 // MessageCore::ImageCollector ic;
211 // ic.collectImagesFrom(mOrigMsg.data());
212
213 // Now, delete the old content and set the new content, which
214 // is either only the new text or the new text with some attachments.
215 auto parts = msg->contents();
216 foreach (KMime::Content *content, parts) {
217 msg->removeContent(content, true/*delete*/);
218 }
219
220 msg->contentType()->clear(); // to get rid of old boundary
221
222 const QByteArray boundary = KMime::multiPartBoundary();
223 KMime::Content *const mainTextPart =
224 htmlBody.isEmpty() ?
225 createPlainPartContent(msg, plainBody) :
226 createMultipartAlternativeContent(msg, plainBody, htmlBody);
227 mainTextPart->assemble();
228
229 KMime::Content *textPart = mainTextPart;
230 // if (!ic.images().empty()) {
231 // textPart = createMultipartRelated(ic, mainTextPart);
232 // textPart->assemble();
233 // }
234
235 // If we have some attachments, create a multipart/mixed mail and
236 // add the normal body as well as the attachments
237 KMime::Content *mainPart = textPart;
238 //FIXME
239 // if (forward) {
240 // auto attachments = mOrigMsg->attachments();
241 // attachments += mOtp->nodeHelper()->attachmentsOfExtraContents();
242 // if (!attachments.isEmpty()) {
243 // mainPart = createMultipartMixed(attachments, textPart);
244 // mainPart->assemble();
245 // }
246 // }
247
248 msg->setBody(mainPart->encodedBody());
249 msg->setHeader(mainPart->contentType());
250 msg->setHeader(mainPart->contentTransferEncoding());
251 msg->assemble();
252 msg->parse();
253}
254
255QString plainToHtml(const QString &body)
256{
257 QString str = body;
258 str = str.toHtmlEscaped();
259 str.replace(QStringLiteral("\n"), QStringLiteral("<br />\n"));
260 return str;
261}
262
263//TODO implement this function using a DOM tree parser
264void makeValidHtml(QString &body, const QString &headElement)
265{
266 QRegExp regEx;
267 regEx.setMinimal(true);
268 regEx.setPattern(QStringLiteral("<html.*>"));
269
270 if (!body.isEmpty() && !body.contains(regEx)) {
271 regEx.setPattern(QStringLiteral("<body.*>"));
272 if (!body.contains(regEx)) {
273 body = QLatin1String("<body>") + body + QLatin1String("<br/></body>");
274 }
275 regEx.setPattern(QStringLiteral("<head.*>"));
276 if (!body.contains(regEx)) {
277 body = QLatin1String("<head>") + headElement + QLatin1String("</head>") + body;
278 }
279 body = QLatin1String("<html>") + body + QLatin1String("</html>");
280 }
281}
282
283QString stripSignature(const QString &msg)
284{
285 // Following RFC 3676, only > before --
286 // I prefer to not delete a SB instead of delete good mail content.
287 const QRegExp sbDelimiterSearch = QRegExp(QLatin1String("(^|\n)[> ]*-- \n"));
288 // The regular expression to look for prefix change
289 const QRegExp commonReplySearch = QRegExp(QLatin1String("^[ ]*>"));
290
291 QString res = msg;
292 int posDeletingStart = 1; // to start looking at 0
293
294 // While there are SB delimiters (start looking just before the deleted SB)
295 while ((posDeletingStart = res.indexOf(sbDelimiterSearch, posDeletingStart - 1)) >= 0) {
296 QString prefix; // the current prefix
297 QString line; // the line to check if is part of the SB
298 int posNewLine = -1;
299
300 // Look for the SB beginning
301 int posSignatureBlock = res.indexOf(QLatin1Char('-'), posDeletingStart);
302 // The prefix before "-- "$
303 if (res.at(posDeletingStart) == QLatin1Char('\n')) {
304 ++posDeletingStart;
305 }
306
307 prefix = res.mid(posDeletingStart, posSignatureBlock - posDeletingStart);
308 posNewLine = res.indexOf(QLatin1Char('\n'), posSignatureBlock) + 1;
309
310 // now go to the end of the SB
311 while (posNewLine < res.size() && posNewLine > 0) {
312 // handle the undefined case for mid ( x , -n ) where n>1
313 int nextPosNewLine = res.indexOf(QLatin1Char('\n'), posNewLine);
314
315 if (nextPosNewLine < 0) {
316 nextPosNewLine = posNewLine - 1;
317 }
318
319 line = res.mid(posNewLine, nextPosNewLine - posNewLine);
320
321 // check when the SB ends:
322 // * does not starts with prefix or
323 // * starts with prefix+(any substring of prefix)
324 if ((prefix.isEmpty() && line.indexOf(commonReplySearch) < 0) ||
325 (!prefix.isEmpty() && line.startsWith(prefix) &&
326 line.mid(prefix.size()).indexOf(commonReplySearch) < 0)) {
327 posNewLine = res.indexOf(QLatin1Char('\n'), posNewLine) + 1;
328 } else {
329 break; // end of the SB
330 }
331 }
332
333 // remove the SB or truncate when is the last SB
334 if (posNewLine > 0) {
335 res.remove(posDeletingStart, posNewLine - posDeletingStart);
336 } else {
337 res.truncate(posDeletingStart);
338 }
339 }
340
341 return res;
342}
343
344QString plainMessageText(MessageViewer::ObjectTreeParser &otp, bool aStripSignature)
345{
346 QString result = otp.plainTextContent();
347 if (result.isEmpty()) { //HTML-only mails
348 QWebPage doc;
349 doc.mainFrame()->setHtml(otp.htmlContent());
350 result = doc.mainFrame()->toPlainText();
351 }
352
353 if (aStripSignature) {
354 result = stripSignature(result);
355 }
356
357 return result;
358}
359
360QString htmlMessageText(MessageViewer::ObjectTreeParser &otp, bool aStripSignature, QString &headElement)
361{
362 QString htmlElement = otp.htmlContent();
363
364 if (htmlElement.isEmpty()) { //plain mails only
365 QString htmlReplace = otp.plainTextContent().toHtmlEscaped();
366 htmlReplace = htmlReplace.replace(QStringLiteral("\n"), QStringLiteral("<br />"));
367 htmlElement = QStringLiteral("<html><head></head><body>%1</body></html>\n").arg(htmlReplace);
368 }
369
370 //QWebPage relies on this
371 Q_ASSERT(QApplication::style());
372 QWebPage page;
373 page.settings()->setAttribute(QWebSettings::JavascriptEnabled, false);
374 page.settings()->setAttribute(QWebSettings::JavaEnabled, false);
375 page.settings()->setAttribute(QWebSettings::PluginsEnabled, false);
376 page.settings()->setAttribute(QWebSettings::AutoLoadImages, false);
377
378 page.currentFrame()->setHtml(htmlElement);
379
380 //TODO to be tested/verified if this is not an issue
381 page.settings()->setAttribute(QWebSettings::JavascriptEnabled, true);
382 const QString bodyElement = page.currentFrame()->evaluateJavaScript(
383 QStringLiteral("document.getElementsByTagName('body')[0].innerHTML")).toString();
384
385 headElement = page.currentFrame()->evaluateJavaScript(
386 QStringLiteral("document.getElementsByTagName('head')[0].innerHTML")).toString();
387
388 page.settings()->setAttribute(QWebSettings::JavascriptEnabled, false);
389
390 if (!bodyElement.isEmpty()) {
391 if (aStripSignature) {
392 //FIXME strip signature works partially for HTML mails
393 return stripSignature(bodyElement);
394 }
395 return bodyElement;
396 }
397
398 if (aStripSignature) {
399 //FIXME strip signature works partially for HTML mails
400 return stripSignature(htmlElement);
401 }
402 return htmlElement;
403}
404
405QString formatQuotePrefix(const QString &wildString, const QString &fromDisplayString)
406{
407 QString result;
408
409 if (wildString.isEmpty()) {
410 return wildString;
411 }
412
413 unsigned int strLength(wildString.length());
414 for (uint i = 0; i < strLength;) {
415 QChar ch = wildString[i++];
416 if (ch == QLatin1Char('%') && i < strLength) {
417 ch = wildString[i++];
418 switch (ch.toLatin1()) {
419 case 'f': { // sender's initals
420 if (fromDisplayString.isEmpty()) {
421 break;
422 }
423
424 uint j = 0;
425 const unsigned int strLength(fromDisplayString.length());
426 for (; j < strLength && fromDisplayString[j] > QLatin1Char(' '); ++j)
427 ;
428 for (; j < strLength && fromDisplayString[j] <= QLatin1Char(' '); ++j)
429 ;
430 result += fromDisplayString[0];
431 if (j < strLength && fromDisplayString[j] > QLatin1Char(' ')) {
432 result += fromDisplayString[j];
433 } else if (strLength > 1) {
434 if (fromDisplayString[1] > QLatin1Char(' ')) {
435 result += fromDisplayString[1];
436 }
437 }
438 }
439 break;
440 case '_':
441 result += QLatin1Char(' ');
442 break;
443 case '%':
444 result += QLatin1Char('%');
445 break;
446 default:
447 result += QLatin1Char('%');
448 result += ch;
449 break;
450 }
451 } else {
452 result += ch;
453 }
454 }
455 return result;
456}
457
458QString quotedPlainText(const QString &selection, const QString &fromDisplayString)
459{
460 QString content = selection;
461 // Remove blank lines at the beginning:
462 const int firstNonWS = content.indexOf(QRegExp(QLatin1String("\\S")));
463 const int lineStart = content.lastIndexOf(QLatin1Char('\n'), firstNonWS);
464 if (lineStart >= 0) {
465 content.remove(0, static_cast<unsigned int>(lineStart));
466 }
467
468 const auto quoteString = QStringLiteral("> ");
469 const QString indentStr = formatQuotePrefix(quoteString, fromDisplayString);
470 //FIXME
471 // if (TemplateParserSettings::self()->smartQuote() && mWrap) {
472 // content = MessageCore::StringUtil::smartQuote(content, mColWrap - indentStr.length());
473 // }
474 content.replace(QLatin1Char('\n'), QLatin1Char('\n') + indentStr);
475 content.prepend(indentStr);
476 content += QLatin1Char('\n');
477
478 return content;
479}
480
481QString quotedHtmlText(const QString &selection)
482{
483 QString content = selection;
484 //TODO 1) look for all the variations of <br> and remove the blank lines
485 //2) implement vertical bar for quoted HTML mail.
486 //3) After vertical bar is implemented, If a user wants to edit quoted message,
487 // then the <blockquote> tags below should open and close as when required.
488
489 //Add blockquote tag, so that quoted message can be differentiated from normal message
490 content = QLatin1String("<blockquote>") + content + QLatin1String("</blockquote>");
491 return content;
492}
493
494void applyCharset(const KMime::Message::Ptr msg, const KMime::Message::Ptr &origMsg)
495{
496 // first convert the body from its current encoding to unicode representation
497 QTextCodec *bodyCodec = KCharsets::charsets()->codecForName(QString::fromLatin1(msg->contentType()->charset()));
498 if (!bodyCodec) {
499 bodyCodec = KCharsets::charsets()->codecForName(QStringLiteral("UTF-8"));
500 }
501
502 const QString body = bodyCodec->toUnicode(msg->body());
503
504 // then apply the encoding of the original message
505 msg->contentType()->setCharset(origMsg->contentType()->charset());
506
507 QTextCodec *codec = KCharsets::charsets()->codecForName(QString::fromLatin1(msg->contentType()->charset()));
508 if (!codec) {
509 qCritical() << "Could not get text codec for charset" << msg->contentType()->charset();
510 } else if (!codec->canEncode(body)) { // charset can't encode body, fall back to preferred
511 const QStringList charsets /*= preferredCharsets() */;
512
513 QList<QByteArray> chars;
514 chars.reserve(charsets.count());
515 foreach (const QString &charset, charsets) {
516 chars << charset.toLatin1();
517 }
518
519 //FIXME
520 QByteArray fallbackCharset/* = selectCharset(chars, body)*/;
521 if (fallbackCharset.isEmpty()) { // UTF-8 as fall-through
522 fallbackCharset = "UTF-8";
523 }
524
525 codec = KCharsets::charsets()->codecForName(QString::fromLatin1(fallbackCharset));
526 msg->setBody(codec->fromUnicode(body));
527 } else {
528 msg->setBody(codec->fromUnicode(body));
529 }
530}
531
532enum ReplyStrategy {
533 ReplyList,
534 ReplySmart,
535 ReplyAll,
536 ReplyAuthor,
537 ReplyNone
538};
539
540KMime::Message::Ptr MailTemplates::reply(const KMime::Message::Ptr &origMsg)
541{
542 //FIXME
543 const bool alwaysPlain = true;
544 //FIXME
545 const ReplyStrategy replyStrategy = ReplySmart;
546 KMime::Message::Ptr msg(new KMime::Message);
547 //FIXME
548 //Personal email addresses
549 KMime::Types::AddrSpecList me;
550 KMime::Types::Mailbox::List toList;
551 KMime::Types::Mailbox::List replyToList;
552 KMime::Types::Mailbox::List mailingListAddresses;
553
554 // const uint originalIdentity = identityUoid(origMsg);
555 initHeader(msg);
556 replyToList = origMsg->replyTo()->mailboxes();
557
558 msg->contentType()->setCharset("utf-8");
559
560 if (origMsg->headerByType("List-Post") &&
561 origMsg->headerByType("List-Post")->asUnicodeString().contains(QStringLiteral("mailto:"), Qt::CaseInsensitive)) {
562
563 const QString listPost = origMsg->headerByType("List-Post")->asUnicodeString();
564 QRegExp rx(QStringLiteral("<mailto:([^@>]+)@([^>]+)>"), Qt::CaseInsensitive);
565 if (rx.indexIn(listPost, 0) != -1) { // matched
566 KMime::Types::Mailbox mailbox;
567 mailbox.fromUnicodeString(rx.cap(1) + QLatin1Char('@') + rx.cap(2));
568 mailingListAddresses << mailbox;
569 }
570 }
571
572 switch (replyStrategy) {
573 case ReplySmart: {
574 if (auto hdr = origMsg->headerByType("Mail-Followup-To")) {
575 toList << KMime::Types::Mailbox::listFrom7BitString(hdr->as7BitString(false));
576 } else if (!replyToList.isEmpty()) {
577 toList = replyToList;
578 } else if (!mailingListAddresses.isEmpty()) {
579 toList = (KMime::Types::Mailbox::List() << mailingListAddresses.at(0));
580 } else {
581 // doesn't seem to be a mailing list, reply to From: address
582 toList = origMsg->from()->mailboxes();
583
584 bool listContainsMe = false;
585 for (const auto &m : me) {
586 KMime::Types::Mailbox mailbox;
587 mailbox.setAddress(m);
588 if (toList.contains(mailbox)) {
589 listContainsMe = true;
590 }
591 }
592 if (listContainsMe) {
593 // sender seems to be one of our own identities, so we assume that this
594 // is a reply to a "sent" mail where the users wants to add additional
595 // information for the recipient.
596 toList = origMsg->to()->mailboxes();
597 }
598 }
599 // strip all my addresses from the list of recipients
600 const KMime::Types::Mailbox::List recipients = toList;
601
602 toList = stripMyAddressesFromAddressList(recipients, me);
603
604 // ... unless the list contains only my addresses (reply to self)
605 if (toList.isEmpty() && !recipients.isEmpty()) {
606 toList << recipients.first();
607 }
608 }
609 break;
610 case ReplyList: {
611 if (auto hdr = origMsg->headerByType("Mail-Followup-To")) {
612 KMime::Types::Mailbox mailbox;
613 mailbox.from7BitString(hdr->as7BitString(false));
614 toList << mailbox;
615 } else if (!mailingListAddresses.isEmpty()) {
616 toList << mailingListAddresses[ 0 ];
617 } else if (!replyToList.isEmpty()) {
618 // assume a Reply-To header mangling mailing list
619 toList = replyToList;
620 }
621
622 //FIXME
623 // strip all my addresses from the list of recipients
624 const KMime::Types::Mailbox::List recipients = toList;
625 toList = stripMyAddressesFromAddressList(recipients, me);
626 }
627 break;
628 case ReplyAll: {
629 KMime::Types::Mailbox::List recipients;
630 KMime::Types::Mailbox::List ccRecipients;
631
632 // add addresses from the Reply-To header to the list of recipients
633 if (!replyToList.isEmpty()) {
634 recipients = replyToList;
635
636 // strip all possible mailing list addresses from the list of Reply-To addresses
637 foreach (const KMime::Types::Mailbox &mailbox, mailingListAddresses) {
638 foreach (const KMime::Types::Mailbox &recipient, recipients) {
639 if (mailbox == recipient) {
640 recipients.removeAll(recipient);
641 }
642 }
643 }
644 }
645
646 if (!mailingListAddresses.isEmpty()) {
647 // this is a mailing list message
648 if (recipients.isEmpty() && !origMsg->from()->asUnicodeString().isEmpty()) {
649 // The sender didn't set a Reply-to address, so we add the From
650 // address to the list of CC recipients.
651 ccRecipients += origMsg->from()->mailboxes();
652 qDebug() << "Added" << origMsg->from()->asUnicodeString() << "to the list of CC recipients";
653 }
654
655 // if it is a mailing list, add the posting address
656 recipients.prepend(mailingListAddresses[ 0 ]);
657 } else {
658 // this is a normal message
659 if (recipients.isEmpty() && !origMsg->from()->asUnicodeString().isEmpty()) {
660 // in case of replying to a normal message only then add the From
661 // address to the list of recipients if there was no Reply-to address
662 recipients += origMsg->from()->mailboxes();
663 qDebug() << "Added" << origMsg->from()->asUnicodeString() << "to the list of recipients";
664 }
665 }
666
667 // strip all my addresses from the list of recipients
668 toList = stripMyAddressesFromAddressList(recipients, me);
669
670 // merge To header and CC header into a list of CC recipients
671 if (!origMsg->cc()->asUnicodeString().isEmpty() || !origMsg->to()->asUnicodeString().isEmpty()) {
672 KMime::Types::Mailbox::List list;
673 if (!origMsg->to()->asUnicodeString().isEmpty()) {
674 list += origMsg->to()->mailboxes();
675 }
676 if (!origMsg->cc()->asUnicodeString().isEmpty()) {
677 list += origMsg->cc()->mailboxes();
678 }
679
680 foreach (const KMime::Types::Mailbox &mailbox, list) {
681 if (!recipients.contains(mailbox) &&
682 !ccRecipients.contains(mailbox)) {
683 ccRecipients += mailbox;
684 qDebug() << "Added" << mailbox.prettyAddress() << "to the list of CC recipients";
685 }
686 }
687 }
688
689 if (!ccRecipients.isEmpty()) {
690 // strip all my addresses from the list of CC recipients
691 ccRecipients = stripMyAddressesFromAddressList(ccRecipients, me);
692
693 // in case of a reply to self, toList might be empty. if that's the case
694 // then propagate a cc recipient to To: (if there is any).
695 if (toList.isEmpty() && !ccRecipients.isEmpty()) {
696 toList << ccRecipients.at(0);
697 ccRecipients.pop_front();
698 }
699
700 foreach (const KMime::Types::Mailbox &mailbox, ccRecipients) {
701 msg->cc()->addAddress(mailbox);
702 }
703 }
704
705 if (toList.isEmpty() && !recipients.isEmpty()) {
706 // reply to self without other recipients
707 toList << recipients.at(0);
708 }
709 }
710 break;
711 case ReplyAuthor: {
712 if (!replyToList.isEmpty()) {
713 KMime::Types::Mailbox::List recipients = replyToList;
714
715 // strip the mailing list post address from the list of Reply-To
716 // addresses since we want to reply in private
717 foreach (const KMime::Types::Mailbox &mailbox, mailingListAddresses) {
718 foreach (const KMime::Types::Mailbox &recipient, recipients) {
719 if (mailbox == recipient) {
720 recipients.removeAll(recipient);
721 }
722 }
723 }
724
725 if (!recipients.isEmpty()) {
726 toList = recipients;
727 } else {
728 // there was only the mailing list post address in the Reply-To header,
729 // so use the From address instead
730 toList = origMsg->from()->mailboxes();
731 }
732 } else if (!origMsg->from()->asUnicodeString().isEmpty()) {
733 toList = origMsg->from()->mailboxes();
734 }
735 }
736 break;
737 case ReplyNone:
738 // the addressees will be set by the caller
739 break;
740 }
741
742 foreach (const KMime::Types::Mailbox &mailbox, toList) {
743 msg->to()->addAddress(mailbox);
744 }
745
746 const QByteArray refStr = getRefStr(origMsg);
747 if (!refStr.isEmpty()) {
748 msg->references()->fromUnicodeString(QString::fromLocal8Bit(refStr), "utf-8");
749 }
750
751 //In-Reply-To = original msg-id
752 msg->inReplyTo()->from7BitString(origMsg->messageID()->as7BitString(false));
753
754 msg->subject()->fromUnicodeString(replySubject(origMsg), "utf-8");
755
756 auto definedLocale = QLocale::system();
757
758 //TODO set empty source instead
759 StringHtmlWriter htmlWriter;
760 QImage paintDevice;
761 CSSHelper cssHelper(&paintDevice);
762 MessageViewer::NodeHelper nodeHelper;
763 ObjectTreeSource source(&htmlWriter, &cssHelper);
764 MessageViewer::ObjectTreeParser otp(&source, &nodeHelper);
765 otp.setAllowAsync(false);
766 otp.parseObjectTree(origMsg.data());
767
768 //Add quoted body
769 QString plainBody;
770 QString htmlBody;
771
772 //On $datetime you wrote:
773 const QDateTime date = origMsg->date()->dateTime();
774 const auto dateTimeString = QString("%1 %2").arg(definedLocale.toString(date.date(), QLocale::LongFormat)).arg(definedLocale.toString(date.time(), QLocale::LongFormat));
775 const auto onDateYouWroteLine = QString("On %1 you wrote:").arg(dateTimeString);
776 plainBody.append(onDateYouWroteLine);
777 htmlBody.append(plainToHtml(onDateYouWroteLine));
778
779 //Strip signature for replies
780 const bool stripSignature = true;
781
782 //Quoted body
783 QString plainQuote = quotedPlainText(plainMessageText(otp, stripSignature), origMsg->from()->displayString());
784 if (plainQuote.endsWith(QLatin1Char('\n'))) {
785 plainQuote.chop(1);
786 }
787 plainBody.append(plainQuote);
788 QString headElement;
789 htmlBody.append(quotedHtmlText(htmlMessageText(otp, stripSignature, headElement)));
790
791 if (alwaysPlain) {
792 htmlBody.clear();
793 } else {
794 makeValidHtml(htmlBody, headElement);
795 }
796
797 addProcessedBodyToMessage(msg, plainBody, htmlBody, false);
798
799 applyCharset(msg, origMsg);
800
801 msg->assemble();
802
803 return msg;
804}