summaryrefslogtreecommitdiffstats
path: root/framework
diff options
context:
space:
mode:
Diffstat (limited to 'framework')
-rw-r--r--framework/mail/CMakeLists.txt3
-rw-r--r--framework/mail/composer.cpp33
-rw-r--r--framework/mail/composer.h7
-rw-r--r--framework/mail/mailtemplates.cpp801
-rw-r--r--framework/mail/mailtemplates.h28
5 files changed, 869 insertions, 3 deletions
diff --git a/framework/mail/CMakeLists.txt b/framework/mail/CMakeLists.txt
index 29fda0e4..822b2981 100644
--- a/framework/mail/CMakeLists.txt
+++ b/framework/mail/CMakeLists.txt
@@ -13,6 +13,7 @@ set(mailplugin_SRCS
13 composer.cpp 13 composer.cpp
14 messageparser.cpp 14 messageparser.cpp
15 mailtransport.cpp 15 mailtransport.cpp
16 mailtemplates.cpp
16 retriever.cpp 17 retriever.cpp
17) 18)
18add_definitions(-DMAIL_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data") 19add_definitions(-DMAIL_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data")
@@ -23,7 +24,7 @@ include_directories(${CURL_INCLUDE_DIRS})
23 24
24add_library(mailplugin SHARED ${mailplugin_SRCS}) 25add_library(mailplugin SHARED ${mailplugin_SRCS})
25 26
26qt5_use_modules(mailplugin Core Quick Qml) 27qt5_use_modules(mailplugin Core Quick Qml WebKitWidgets)
27 28
28target_link_libraries(mailplugin actionplugin settingsplugin sink KF5::Otp KF5::Codecs ${CURL_LIBRARIES}) 29target_link_libraries(mailplugin actionplugin settingsplugin sink KF5::Otp KF5::Codecs ${CURL_LIBRARIES})
29 30
diff --git a/framework/mail/composer.cpp b/framework/mail/composer.cpp
index 4ef112fa..9edc8345 100644
--- a/framework/mail/composer.cpp
+++ b/framework/mail/composer.cpp
@@ -27,6 +27,8 @@
27#include <QVariant> 27#include <QVariant>
28#include <QDebug> 28#include <QDebug>
29 29
30#include "mailtemplates.h"
31
30Composer::Composer(QObject *parent) : QObject(parent) 32Composer::Composer(QObject *parent) : QObject(parent)
31{ 33{
32 m_identityModel << "Kuberich <kuberich@kolabnow.com>" << "Uni <kuberich@university.edu>" << "Spam <hello.spam@spam.to>"; 34 m_identityModel << "Kuberich <kuberich@kolabnow.com>" << "Uni <kuberich@university.edu>" << "Spam <hello.spam@spam.to>";
@@ -115,9 +117,36 @@ void Composer::setFromIndex(int fromIndex)
115 } 117 }
116} 118}
117 119
120QVariant Composer::originalMessage() const
121{
122 return m_originalMessage;
123}
124
125void Composer::setOriginalMessage(const QVariant &originalMessage)
126{
127 const auto mailData = KMime::CRLFtoLF(originalMessage.toByteArray());
128 if (!mailData.isEmpty()) {
129 KMime::Message::Ptr mail(new KMime::Message);
130 mail->setContent(mailData);
131 mail->parse();
132 auto reply = MailTemplates::reply(mail);
133 //We assume reply
134 setTo(reply->to(true)->asUnicodeString());
135 setCc(reply->cc(true)->asUnicodeString());
136 setSubject(reply->subject(true)->asUnicodeString());
137 setBody(reply->body());
138 m_msg = QVariant::fromValue(reply);
139 } else {
140 m_msg = QVariant();
141 }
142}
143
118void Composer::send() 144void Composer::send()
119{ 145{
120 auto mail = KMime::Message::Ptr::create(); 146 auto mail = m_msg.value<KMime::Message::Ptr>();
147 if (!mail) {
148 mail = KMime::Message::Ptr::create();
149 }
121 for (const auto &to : KEmailAddress::splitAddressList(m_to)) { 150 for (const auto &to : KEmailAddress::splitAddressList(m_to)) {
122 QByteArray displayName; 151 QByteArray displayName;
123 QByteArray addrSpec; 152 QByteArray addrSpec;
@@ -159,4 +188,4 @@ void Composer::clear()
159 setCc(""); 188 setCc("");
160 setBcc(""); 189 setBcc("");
161 setFromIndex(-1); 190 setFromIndex(-1);
162} \ No newline at end of file 191}
diff --git a/framework/mail/composer.h b/framework/mail/composer.h
index bdb59840..ee38187f 100644
--- a/framework/mail/composer.h
+++ b/framework/mail/composer.h
@@ -22,10 +22,12 @@
22#include <QObject> 22#include <QObject>
23#include <QString> 23#include <QString>
24#include <QStringList> 24#include <QStringList>
25#include <QVariant>
25 26
26class Composer : public QObject 27class Composer : public QObject
27{ 28{
28 Q_OBJECT 29 Q_OBJECT
30 Q_PROPERTY (QVariant originalMessage READ originalMessage WRITE setOriginalMessage)
29 Q_PROPERTY (QString to READ to WRITE setTo NOTIFY toChanged) 31 Q_PROPERTY (QString to READ to WRITE setTo NOTIFY toChanged)
30 Q_PROPERTY (QString cc READ cc WRITE setCc NOTIFY ccChanged) 32 Q_PROPERTY (QString cc READ cc WRITE setCc NOTIFY ccChanged)
31 Q_PROPERTY (QString bcc READ bcc WRITE setBcc NOTIFY bccChanged) 33 Q_PROPERTY (QString bcc READ bcc WRITE setBcc NOTIFY bccChanged)
@@ -57,6 +59,9 @@ public:
57 int fromIndex() const; 59 int fromIndex() const;
58 void setFromIndex(int fromIndex); 60 void setFromIndex(int fromIndex);
59 61
62 QVariant originalMessage() const;
63 void setOriginalMessage(const QVariant &originalMessage);
64
60signals: 65signals:
61 void subjectChanged(); 66 void subjectChanged();
62 void bodyChanged(); 67 void bodyChanged();
@@ -78,4 +83,6 @@ private:
78 QString m_body; 83 QString m_body;
79 QStringList m_identityModel; 84 QStringList m_identityModel;
80 int m_fromIndex; 85 int m_fromIndex;
86 QVariant m_originalMessage;
87 QVariant m_msg;
81}; 88};
diff --git a/framework/mail/mailtemplates.cpp b/framework/mail/mailtemplates.cpp
new file mode 100644
index 00000000..7cbd887f
--- /dev/null
+++ b/framework/mail/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
32#include <KCodecs/KCharsets>
33#include <KMime/Types>
34
35#include "stringhtmlwriter.h"
36#include "objecttreesource.h"
37#include "csshelper.h"
38
39#include <MessageViewer/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(MessageViewer::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(MessageViewer::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 page;
370 page.settings()->setAttribute(QWebSettings::JavascriptEnabled, false);
371 page.settings()->setAttribute(QWebSettings::JavaEnabled, false);
372 page.settings()->setAttribute(QWebSettings::PluginsEnabled, false);
373 page.settings()->setAttribute(QWebSettings::AutoLoadImages, false);
374
375 page.currentFrame()->setHtml(htmlElement);
376
377 //TODO to be tested/verified if this is not an issue
378 page.settings()->setAttribute(QWebSettings::JavascriptEnabled, true);
379 const QString bodyElement = page.currentFrame()->evaluateJavaScript(
380 QStringLiteral("document.getElementsByTagName('body')[0].innerHTML")).toString();
381
382 headElement = page.currentFrame()->evaluateJavaScript(
383 QStringLiteral("document.getElementsByTagName('head')[0].innerHTML")).toString();
384
385 page.settings()->setAttribute(QWebSettings::JavascriptEnabled, false);
386
387 if (!bodyElement.isEmpty()) {
388 if (aStripSignature) {
389 //FIXME strip signature works partially for HTML mails
390 return stripSignature(bodyElement);
391 }
392 return bodyElement;
393 }
394
395 if (aStripSignature) {
396 //FIXME strip signature works partially for HTML mails
397 return stripSignature(htmlElement);
398 }
399 return htmlElement;
400}
401
402QString formatQuotePrefix(const QString &wildString, const QString &fromDisplayString)
403{
404 QString result;
405
406 if (wildString.isEmpty()) {
407 return wildString;
408 }
409
410 unsigned int strLength(wildString.length());
411 for (uint i = 0; i < strLength;) {
412 QChar ch = wildString[i++];
413 if (ch == QLatin1Char('%') && i < strLength) {
414 ch = wildString[i++];
415 switch (ch.toLatin1()) {
416 case 'f': { // sender's initals
417 if (fromDisplayString.isEmpty()) {
418 break;
419 }
420
421 uint j = 0;
422 const unsigned int strLength(fromDisplayString.length());
423 for (; j < strLength && fromDisplayString[j] > QLatin1Char(' '); ++j)
424 ;
425 for (; j < strLength && fromDisplayString[j] <= QLatin1Char(' '); ++j)
426 ;
427 result += fromDisplayString[0];
428 if (j < strLength && fromDisplayString[j] > QLatin1Char(' ')) {
429 result += fromDisplayString[j];
430 } else if (strLength > 1) {
431 if (fromDisplayString[1] > QLatin1Char(' ')) {
432 result += fromDisplayString[1];
433 }
434 }
435 }
436 break;
437 case '_':
438 result += QLatin1Char(' ');
439 break;
440 case '%':
441 result += QLatin1Char('%');
442 break;
443 default:
444 result += QLatin1Char('%');
445 result += ch;
446 break;
447 }
448 } else {
449 result += ch;
450 }
451 }
452 return result;
453}
454
455QString quotedPlainText(const QString &selection, const QString &fromDisplayString)
456{
457 QString content = selection;
458 // Remove blank lines at the beginning:
459 const int firstNonWS = content.indexOf(QRegExp(QLatin1String("\\S")));
460 const int lineStart = content.lastIndexOf(QLatin1Char('\n'), firstNonWS);
461 if (lineStart >= 0) {
462 content.remove(0, static_cast<unsigned int>(lineStart));
463 }
464
465 const auto quoteString = QStringLiteral("> ");
466 const QString indentStr = formatQuotePrefix(quoteString, fromDisplayString);
467 //FIXME
468 // if (TemplateParserSettings::self()->smartQuote() && mWrap) {
469 // content = MessageCore::StringUtil::smartQuote(content, mColWrap - indentStr.length());
470 // }
471 content.replace(QLatin1Char('\n'), QLatin1Char('\n') + indentStr);
472 content.prepend(indentStr);
473 content += QLatin1Char('\n');
474
475 return content;
476}
477
478QString quotedHtmlText(const QString &selection)
479{
480 QString content = selection;
481 //TODO 1) look for all the variations of <br> and remove the blank lines
482 //2) implement vertical bar for quoted HTML mail.
483 //3) After vertical bar is implemented, If a user wants to edit quoted message,
484 // then the <blockquote> tags below should open and close as when required.
485
486 //Add blockquote tag, so that quoted message can be differentiated from normal message
487 content = QLatin1String("<blockquote>") + content + QLatin1String("</blockquote>");
488 return content;
489}
490
491void applyCharset(const KMime::Message::Ptr msg, const KMime::Message::Ptr &origMsg)
492{
493 // first convert the body from its current encoding to unicode representation
494 QTextCodec *bodyCodec = KCharsets::charsets()->codecForName(QString::fromLatin1(msg->contentType()->charset()));
495 if (!bodyCodec) {
496 bodyCodec = KCharsets::charsets()->codecForName(QStringLiteral("UTF-8"));
497 }
498
499 const QString body = bodyCodec->toUnicode(msg->body());
500
501 // then apply the encoding of the original message
502 msg->contentType()->setCharset(origMsg->contentType()->charset());
503
504 QTextCodec *codec = KCharsets::charsets()->codecForName(QString::fromLatin1(msg->contentType()->charset()));
505 if (!codec) {
506 qCritical() << "Could not get text codec for charset" << msg->contentType()->charset();
507 } else if (!codec->canEncode(body)) { // charset can't encode body, fall back to preferred
508 const QStringList charsets /*= preferredCharsets() */;
509
510 QList<QByteArray> chars;
511 chars.reserve(charsets.count());
512 foreach (const QString &charset, charsets) {
513 chars << charset.toLatin1();
514 }
515
516 //FIXME
517 QByteArray fallbackCharset/* = selectCharset(chars, body)*/;
518 if (fallbackCharset.isEmpty()) { // UTF-8 as fall-through
519 fallbackCharset = "UTF-8";
520 }
521
522 codec = KCharsets::charsets()->codecForName(QString::fromLatin1(fallbackCharset));
523 msg->setBody(codec->fromUnicode(body));
524 } else {
525 msg->setBody(codec->fromUnicode(body));
526 }
527}
528
529enum ReplyStrategy {
530 ReplyList,
531 ReplySmart,
532 ReplyAll,
533 ReplyAuthor,
534 ReplyNone
535};
536
537KMime::Message::Ptr MailTemplates::reply(const KMime::Message::Ptr &origMsg)
538{
539 //FIXME
540 const bool alwaysPlain = true;
541 //FIXME
542 const ReplyStrategy replyStrategy = ReplySmart;
543 KMime::Message::Ptr msg(new KMime::Message);
544 //FIXME
545 //Personal email addresses
546 KMime::Types::AddrSpecList me;
547 KMime::Types::Mailbox::List toList;
548 KMime::Types::Mailbox::List replyToList;
549 KMime::Types::Mailbox::List mailingListAddresses;
550
551 // const uint originalIdentity = identityUoid(origMsg);
552 initHeader(msg);
553 replyToList = origMsg->replyTo()->mailboxes();
554
555 msg->contentType()->setCharset("utf-8");
556
557 if (origMsg->headerByType("List-Post") &&
558 origMsg->headerByType("List-Post")->asUnicodeString().contains(QStringLiteral("mailto:"), Qt::CaseInsensitive)) {
559
560 const QString listPost = origMsg->headerByType("List-Post")->asUnicodeString();
561 QRegExp rx(QStringLiteral("<mailto:([^@>]+)@([^>]+)>"), Qt::CaseInsensitive);
562 if (rx.indexIn(listPost, 0) != -1) { // matched
563 KMime::Types::Mailbox mailbox;
564 mailbox.fromUnicodeString(rx.cap(1) + QLatin1Char('@') + rx.cap(2));
565 mailingListAddresses << mailbox;
566 }
567 }
568
569 switch (replyStrategy) {
570 case ReplySmart: {
571 if (auto hdr = origMsg->headerByType("Mail-Followup-To")) {
572 toList << KMime::Types::Mailbox::listFrom7BitString(hdr->as7BitString(false));
573 } else if (!replyToList.isEmpty()) {
574 toList = replyToList;
575 } else if (!mailingListAddresses.isEmpty()) {
576 toList = (KMime::Types::Mailbox::List() << mailingListAddresses.at(0));
577 } else {
578 // doesn't seem to be a mailing list, reply to From: address
579 toList = origMsg->from()->mailboxes();
580
581 bool listContainsMe = false;
582 for (const auto &m : me) {
583 KMime::Types::Mailbox mailbox;
584 mailbox.setAddress(m);
585 if (toList.contains(mailbox)) {
586 listContainsMe = true;
587 }
588 }
589 if (listContainsMe) {
590 // sender seems to be one of our own identities, so we assume that this
591 // is a reply to a "sent" mail where the users wants to add additional
592 // information for the recipient.
593 toList = origMsg->to()->mailboxes();
594 }
595 }
596 // strip all my addresses from the list of recipients
597 const KMime::Types::Mailbox::List recipients = toList;
598
599 toList = stripMyAddressesFromAddressList(recipients, me);
600
601 // ... unless the list contains only my addresses (reply to self)
602 if (toList.isEmpty() && !recipients.isEmpty()) {
603 toList << recipients.first();
604 }
605 }
606 break;
607 case ReplyList: {
608 if (auto hdr = origMsg->headerByType("Mail-Followup-To")) {
609 KMime::Types::Mailbox mailbox;
610 mailbox.from7BitString(hdr->as7BitString(false));
611 toList << mailbox;
612 } else if (!mailingListAddresses.isEmpty()) {
613 toList << mailingListAddresses[ 0 ];
614 } else if (!replyToList.isEmpty()) {
615 // assume a Reply-To header mangling mailing list
616 toList = replyToList;
617 }
618
619 //FIXME
620 // strip all my addresses from the list of recipients
621 const KMime::Types::Mailbox::List recipients = toList;
622 toList = stripMyAddressesFromAddressList(recipients, me);
623 }
624 break;
625 case ReplyAll: {
626 KMime::Types::Mailbox::List recipients;
627 KMime::Types::Mailbox::List ccRecipients;
628
629 // add addresses from the Reply-To header to the list of recipients
630 if (!replyToList.isEmpty()) {
631 recipients = replyToList;
632
633 // strip all possible mailing list addresses from the list of Reply-To addresses
634 foreach (const KMime::Types::Mailbox &mailbox, mailingListAddresses) {
635 foreach (const KMime::Types::Mailbox &recipient, recipients) {
636 if (mailbox == recipient) {
637 recipients.removeAll(recipient);
638 }
639 }
640 }
641 }
642
643 if (!mailingListAddresses.isEmpty()) {
644 // this is a mailing list message
645 if (recipients.isEmpty() && !origMsg->from()->asUnicodeString().isEmpty()) {
646 // The sender didn't set a Reply-to address, so we add the From
647 // address to the list of CC recipients.
648 ccRecipients += origMsg->from()->mailboxes();
649 qDebug() << "Added" << origMsg->from()->asUnicodeString() << "to the list of CC recipients";
650 }
651
652 // if it is a mailing list, add the posting address
653 recipients.prepend(mailingListAddresses[ 0 ]);
654 } else {
655 // this is a normal message
656 if (recipients.isEmpty() && !origMsg->from()->asUnicodeString().isEmpty()) {
657 // in case of replying to a normal message only then add the From
658 // address to the list of recipients if there was no Reply-to address
659 recipients += origMsg->from()->mailboxes();
660 qDebug() << "Added" << origMsg->from()->asUnicodeString() << "to the list of recipients";
661 }
662 }
663
664 // strip all my addresses from the list of recipients
665 toList = stripMyAddressesFromAddressList(recipients, me);
666
667 // merge To header and CC header into a list of CC recipients
668 if (!origMsg->cc()->asUnicodeString().isEmpty() || !origMsg->to()->asUnicodeString().isEmpty()) {
669 KMime::Types::Mailbox::List list;
670 if (!origMsg->to()->asUnicodeString().isEmpty()) {
671 list += origMsg->to()->mailboxes();
672 }
673 if (!origMsg->cc()->asUnicodeString().isEmpty()) {
674 list += origMsg->cc()->mailboxes();
675 }
676
677 foreach (const KMime::Types::Mailbox &mailbox, list) {
678 if (!recipients.contains(mailbox) &&
679 !ccRecipients.contains(mailbox)) {
680 ccRecipients += mailbox;
681 qDebug() << "Added" << mailbox.prettyAddress() << "to the list of CC recipients";
682 }
683 }
684 }
685
686 if (!ccRecipients.isEmpty()) {
687 // strip all my addresses from the list of CC recipients
688 ccRecipients = stripMyAddressesFromAddressList(ccRecipients, me);
689
690 // in case of a reply to self, toList might be empty. if that's the case
691 // then propagate a cc recipient to To: (if there is any).
692 if (toList.isEmpty() && !ccRecipients.isEmpty()) {
693 toList << ccRecipients.at(0);
694 ccRecipients.pop_front();
695 }
696
697 foreach (const KMime::Types::Mailbox &mailbox, ccRecipients) {
698 msg->cc()->addAddress(mailbox);
699 }
700 }
701
702 if (toList.isEmpty() && !recipients.isEmpty()) {
703 // reply to self without other recipients
704 toList << recipients.at(0);
705 }
706 }
707 break;
708 case ReplyAuthor: {
709 if (!replyToList.isEmpty()) {
710 KMime::Types::Mailbox::List recipients = replyToList;
711
712 // strip the mailing list post address from the list of Reply-To
713 // addresses since we want to reply in private
714 foreach (const KMime::Types::Mailbox &mailbox, mailingListAddresses) {
715 foreach (const KMime::Types::Mailbox &recipient, recipients) {
716 if (mailbox == recipient) {
717 recipients.removeAll(recipient);
718 }
719 }
720 }
721
722 if (!recipients.isEmpty()) {
723 toList = recipients;
724 } else {
725 // there was only the mailing list post address in the Reply-To header,
726 // so use the From address instead
727 toList = origMsg->from()->mailboxes();
728 }
729 } else if (!origMsg->from()->asUnicodeString().isEmpty()) {
730 toList = origMsg->from()->mailboxes();
731 }
732 }
733 break;
734 case ReplyNone:
735 // the addressees will be set by the caller
736 break;
737 }
738
739 foreach (const KMime::Types::Mailbox &mailbox, toList) {
740 msg->to()->addAddress(mailbox);
741 }
742
743 const QByteArray refStr = getRefStr(origMsg);
744 if (!refStr.isEmpty()) {
745 msg->references()->fromUnicodeString(QString::fromLocal8Bit(refStr), "utf-8");
746 }
747
748 //In-Reply-To = original msg-id
749 msg->inReplyTo()->from7BitString(origMsg->messageID()->as7BitString(false));
750
751 msg->subject()->fromUnicodeString(replySubject(origMsg), "utf-8");
752
753 auto definedLocale = QLocale::system();
754
755 //TODO set empty source instead
756 StringHtmlWriter htmlWriter;
757 QImage paintDevice;
758 CSSHelper cssHelper(&paintDevice);
759 MessageViewer::NodeHelper nodeHelper;
760 ObjectTreeSource source(&htmlWriter, &cssHelper);
761 MessageViewer::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}
diff --git a/framework/mail/mailtemplates.h b/framework/mail/mailtemplates.h
new file mode 100644
index 00000000..6519122a
--- /dev/null
+++ b/framework/mail/mailtemplates.h
@@ -0,0 +1,28 @@
1/*
2 Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20#pragma once
21
22#include <QByteArray>
23#include <KMime/Message>
24
25namespace MailTemplates
26{
27 KMime::Message::Ptr reply(const KMime::Message::Ptr &message);
28};