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