Messagelib

templateparserjob.cpp
1/*
2 SPDX-FileCopyrightText: 2017-2024 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "templateparserjob.h"
8#include "customtemplates_kfg.h"
9#include "globalsettings_templateparser.h"
10#include "templateparserextracthtmlinfo.h"
11#include "templateparserjob_p.h"
12#include "templatesconfiguration.h"
13#include "templatesconfiguration_kfg.h"
14#include "templatesutil.h"
15#include "templatesutil_p.h"
16
17#include <MessageCore/ImageCollector>
18#include <MessageCore/StringUtil>
19
20#include <MimeTreeParser/MessagePart>
21#include <MimeTreeParser/ObjectTreeParser>
22#include <MimeTreeParser/SimpleObjectTreeSource>
23
24#include <KIdentityManagementCore/Identity>
25#include <KIdentityManagementCore/IdentityManager>
26
27#include "templateparser_debug.h"
28#include <KLocalizedString>
29#include <KMessageBox>
30#include <KProcess>
31#include <KShell>
32
33#include <QDir>
34#include <QFile>
35#include <QFileInfo>
36#include <QLocale>
37#include <QRegularExpression>
38#include <QStringDecoder>
39
40namespace
41{
42Q_DECL_CONSTEXPR inline int pipeTimeout()
43{
44 return 15 * 1000;
45}
46}
47
48using namespace TemplateParser;
49
50TemplateParserJobPrivate::TemplateParserJobPrivate(const KMime::Message::Ptr &amsg, const TemplateParserJob::Mode amode)
51 : mMsg(amsg)
52 , mMode(amode)
53{
55 mEmptySource->setDecryptMessage(mAllowDecryption);
56
57 mOtp = new MimeTreeParser::ObjectTreeParser(mEmptySource);
58 mOtp->setAllowAsync(false);
59}
60
61TemplateParserJobPrivate::~TemplateParserJobPrivate()
62{
63 delete mEmptySource;
64 delete mOtp;
65}
66
67void TemplateParserJobPrivate::setAllowDecryption(const bool allowDecryption)
68{
69 mAllowDecryption = allowDecryption;
70 mEmptySource->setDecryptMessage(mAllowDecryption);
71}
72
73TemplateParserJob::TemplateParserJob(const KMime::Message::Ptr &amsg, const Mode amode, QObject *parent)
74 : QObject(parent)
75 , d(new TemplateParserJobPrivate(amsg, amode))
76{
77}
78
79TemplateParserJob::~TemplateParserJob() = default;
80
82{
83 d->mSelection = selection;
84}
85
86void TemplateParserJob::setAllowDecryption(const bool allowDecryption)
87{
88 d->setAllowDecryption(allowDecryption);
89}
90
91bool TemplateParserJob::shouldStripSignature() const
92{
93 // Only strip the signature when replying, it should be preserved when forwarding
94 return (d->mMode == Reply || d->mMode == ReplyAll) && TemplateParserSettings::self()->stripSignature();
95}
96
98{
99 d->m_identityManager = ident;
100}
101
102int TemplateParserJob::parseQuotes(const QString &prefix, const QString &str, QString &quote)
103{
104 int pos = prefix.length();
105 int len;
106 const int str_len = str.length();
107
108 // Also allow the german lower double-quote sign as quote separator, not only
109 // the standard ASCII quote ("). This fixes bug 166728.
110 const QList<QChar> quoteChars = {QLatin1Char('"'), QChar(0x201C)};
111
112 QChar prev(QChar::Null);
113
114 pos++;
115 len = pos;
116
117 while (pos < str_len) {
118 const QChar c = str[pos];
119
120 pos++;
121 len++;
122
123 if (!prev.isNull()) {
124 quote.append(c);
125 prev = QChar::Null;
126 } else {
127 if (c == QLatin1Char('\\')) {
128 prev = c;
129 } else if (quoteChars.contains(c)) {
130 break;
131 } else {
132 quote.append(c);
133 }
134 }
135 }
136
137 return len;
138}
139
140void TemplateParserJob::process(const KMime::Message::Ptr &aorig_msg, qint64 afolder)
141{
142 if (aorig_msg == nullptr) {
143 qCDebug(TEMPLATEPARSER_LOG) << "aorig_msg == 0!";
144 Q_EMIT parsingDone(d->mForceCursorPosition);
145 deleteLater();
146 return;
147 }
148
149 d->mOrigMsg = aorig_msg;
150 d->mFolder = afolder;
151 const QString tmpl = findTemplate();
152 if (tmpl.isEmpty()) {
153 Q_EMIT parsingDone(d->mForceCursorPosition);
154 deleteLater();
155 return;
156 }
157 processWithTemplate(tmpl);
158}
159
160void TemplateParserJob::process(const QString &tmplName, const KMime::Message::Ptr &aorig_msg, qint64 afolder)
161{
162 d->mForceCursorPosition = false;
163 d->mOrigMsg = aorig_msg;
164 d->mFolder = afolder;
165 const QString tmpl = findCustomTemplate(tmplName);
166 processWithTemplate(tmpl);
167}
168
169void TemplateParserJob::processWithIdentity(uint uoid, const KMime::Message::Ptr &aorig_msg, qint64 afolder)
170{
171 d->mIdentity = uoid;
172 process(aorig_msg, afolder);
173}
174
176{
177 const auto subParts = messageTree->subParts();
178 for (const auto &mp : subParts) {
179 auto text = mp.dynamicCast<MimeTreeParser::TextMessagePart>();
180 const auto attach = mp.dynamicCast<MimeTreeParser::AttachmentMessagePart>();
181 if (text && !attach) {
182 // TextMessagePart can have several subparts cause of PGP inline, we search for the first part with content
183 const auto mpSubParts{mp->subParts()};
184 for (const auto &sub : mpSubParts) {
185 if (!sub->text().trimmed().isEmpty()) {
186 return sub;
187 }
188 }
189 return text;
190 } else if (const auto html = mp.dynamicCast<MimeTreeParser::HtmlMessagePart>()) {
191 return html;
192 } else if (const auto alternative = mp.dynamicCast<MimeTreeParser::AlternativeMessagePart>()) {
193 return alternative;
194 } else {
195 auto ret = toplevelTextNode(mp);
196 if (ret) {
197 return ret;
198 }
199 }
200 }
201 return {};
202}
203
204void TemplateParserJob::processWithTemplate(const QString &tmpl)
205{
206 d->mOtp->parseObjectTree(d->mOrigMsg.data());
207
208 const auto mp = toplevelTextNode(d->mOtp->parsedPart());
209 if (!mp) {
210 qCWarning(TEMPLATEPARSER_LOG) << "Invalid message! mp is null ";
211 Q_EMIT parsingFailed();
212 return;
213 }
214
215 QString plainText = mp->plaintextContent();
216 QString htmlElement;
217
218 if (mp->isHtml()) {
219 htmlElement = d->mOtp->htmlContent();
220 if (plainText.isEmpty()) { // HTML-only mails
221 plainText = htmlElement;
222 }
223 } else { // plain mails only
224 QString htmlReplace = plainText.toHtmlEscaped();
225 htmlReplace.replace(QLatin1Char('\n'), QStringLiteral("<br />"));
226 htmlElement = QStringLiteral("<html><head></head><body>%1</body></html>\n").arg(htmlReplace);
227 }
228
229 auto job = new TemplateParserExtractHtmlInfo(this);
230 connect(job, &TemplateParserExtractHtmlInfo::finished, this, &TemplateParserJob::slotExtractInfoDone);
231
232 job->setHtmlForExtractingTextPlain(plainText);
233 job->setTemplate(tmpl);
234
235 job->setHtmlForExtractionHeaderAndBody(htmlElement);
236 job->start();
237}
238
239void TemplateParserJob::setReplyAsHtml(bool replyAsHtml)
240{
241 d->mReplyAsHtml = replyAsHtml;
242}
243
244void TemplateParserJob::slotExtractInfoDone(const TemplateParserExtractHtmlInfoResult &result)
245{
246 d->mExtractHtmlInfoResult = result;
247 const QString tmpl = d->mExtractHtmlInfoResult.mTemplate;
248 const int tmpl_len = tmpl.length();
249 QString plainBody;
250 QString htmlBody;
251
252 bool dnl = false;
253 auto definedLocale = QLocale();
254 for (int i = 0; i < tmpl_len; ++i) {
255 QChar c = tmpl[i];
256 // qCDebug(TEMPLATEPARSER_LOG) << "Next char: " << c;
257 if (c == QLatin1Char('%')) {
258 const QString cmd = tmpl.mid(i + 1);
259
260 if (cmd.startsWith(QLatin1Char('-'))) {
261 // dnl
262 qCDebug(TEMPLATEPARSER_LOG) << "Command: -";
263 dnl = true;
264 i += 1;
265 } else if (cmd.startsWith(QLatin1StringView("REM="))) {
266 // comments
267 qCDebug(TEMPLATEPARSER_LOG) << "Command: REM=";
268 QString q;
269 const int len = parseQuotes(QStringLiteral("REM="), cmd, q);
270 i += len;
271 } else if (cmd.startsWith(QLatin1StringView("LANGUAGE="))) {
272 QString q;
273 const int len = parseQuotes(QStringLiteral("LANGUAGE="), cmd, q);
274 i += len;
275 if (!q.isEmpty()) {
276 definedLocale = QLocale(q);
277 }
278 } else if (cmd.startsWith(QLatin1StringView("DICTIONARYLANGUAGE="))) {
279 QString q;
280 const int len = parseQuotes(QStringLiteral("DICTIONARYLANGUAGE="), cmd, q);
281 i += len;
282 if (!q.isEmpty()) {
283 auto header = new KMime::Headers::Generic("X-KMail-Dictionary");
284 header->fromUnicodeString(q);
285 d->mMsg->setHeader(header);
286 }
287 } else if (cmd.startsWith(QLatin1StringView("INSERT=")) || cmd.startsWith(QLatin1StringView("PUT="))) {
288 QString q;
289 int len = 0;
290 if (cmd.startsWith(QLatin1StringView("INSERT="))) {
291 // insert content of specified file as is
292 qCDebug(TEMPLATEPARSER_LOG) << "Command: INSERT=";
293 len = parseQuotes(QStringLiteral("INSERT="), cmd, q);
294 } else {
295 // insert content of specified file as is
296 qCDebug(TEMPLATEPARSER_LOG) << "Command: PUT=";
297 len = parseQuotes(QStringLiteral("PUT="), cmd, q);
298 }
299 i += len;
301 QFileInfo finfo(path);
302 if (finfo.isRelative()) {
303 path = QDir::homePath() + QLatin1Char('/') + q;
304 }
305 QFile file(path);
306 if (file.open(QIODevice::ReadOnly)) {
307 const QByteArray content = file.readAll();
308 const QString str = QString::fromLocal8Bit(content.constData(), content.size());
309 plainBody.append(str);
310 const QString body = plainTextToHtml(str);
311 htmlBody.append(body);
312 } else if (d->mDebug) {
313 KMessageBox::error(nullptr, i18nc("@info", "Cannot insert content from file %1: %2", path, file.errorString()));
314 }
315 } else if (cmd.startsWith(QLatin1StringView("SYSTEM="))) {
316 // insert content of specified file as is
317 qCDebug(TEMPLATEPARSER_LOG) << "Command: SYSTEM=";
318 QString q;
319 const int len = parseQuotes(QStringLiteral("SYSTEM="), cmd, q);
320 i += len;
321 const QString pipe_cmd = q;
322 const QString str = pipe(pipe_cmd, QString());
323 plainBody.append(str);
324 const QString body = plainTextToHtml(str);
325 htmlBody.append(body);
326 } else if (cmd.startsWith(QLatin1StringView("QUOTEPIPE="))) {
327 // pipe message body through command and insert it as quotation
328 qCDebug(TEMPLATEPARSER_LOG) << "Command: QUOTEPIPE=";
329 QString q;
330 const int len = parseQuotes(QStringLiteral("QUOTEPIPE="), cmd, q);
331 i += len;
332 const QString pipe_cmd = q;
333 if (d->mOrigMsg) {
334 const QString plainStr = pipe(pipe_cmd, plainMessageText(shouldStripSignature(), NoSelectionAllowed));
335 QString plainQuote = quotedPlainText(plainStr);
336 if (plainQuote.endsWith(QLatin1Char('\n'))) {
337 plainQuote.chop(1);
338 }
339 plainBody.append(plainQuote);
340
341 const QString htmlStr = pipe(pipe_cmd, htmlMessageText(shouldStripSignature(), NoSelectionAllowed));
342 const QString htmlQuote = quotedHtmlText(htmlStr);
343 htmlBody.append(htmlQuote);
344 }
345 } else if (cmd.startsWith(QLatin1StringView("QUOTE"))) {
346 qCDebug(TEMPLATEPARSER_LOG) << "Command: QUOTE";
347 i += strlen("QUOTE");
348 if (d->mOrigMsg) {
349 QString plainQuote = quotedPlainText(plainMessageText(shouldStripSignature(), SelectionAllowed));
350 if (plainQuote.endsWith(QLatin1Char('\n'))) {
351 plainQuote.chop(1);
352 }
353 plainBody.append(plainQuote);
354
355 const QString htmlQuote = quotedHtmlText(htmlMessageText(shouldStripSignature(), SelectionAllowed));
356 htmlBody.append(htmlQuote);
357 }
358 } else if (cmd.startsWith(QLatin1StringView("FORCEDPLAIN"))) {
359 qCDebug(TEMPLATEPARSER_LOG) << "Command: FORCEDPLAIN";
360 d->mQuotes = ReplyAsPlain;
361 i += strlen("FORCEDPLAIN");
362 } else if (cmd.startsWith(QLatin1StringView("FORCEDHTML"))) {
363 qCDebug(TEMPLATEPARSER_LOG) << "Command: FORCEDHTML";
364 d->mQuotes = ReplyAsHtml;
365 i += strlen("FORCEDHTML");
366 } else if (cmd.startsWith(QLatin1StringView("QHEADERS"))) {
367 qCDebug(TEMPLATEPARSER_LOG) << "Command: QHEADERS";
368 i += strlen("QHEADERS");
369 if (d->mOrigMsg) {
371 QString plainQuote = quotedPlainText(headerStr);
372 if (plainQuote.endsWith(QLatin1Char('\n'))) {
373 plainQuote.chop(1);
374 }
375 plainBody.append(plainQuote);
376
377 const QString htmlQuote = quotedHtmlText(headerStr);
378 const QString str = plainTextToHtml(htmlQuote);
379 htmlBody.append(str);
380 }
381 } else if (cmd.startsWith(QLatin1StringView("HEADERS"))) {
382 qCDebug(TEMPLATEPARSER_LOG) << "Command: HEADERS";
383 i += strlen("HEADERS");
384 if (d->mOrigMsg) {
386 plainBody.append(str);
387 const QString body = plainTextToHtml(str);
388 htmlBody.append(body);
389 }
390 } else if (cmd.startsWith(QLatin1StringView("TEXTPIPE="))) {
391 // pipe message body through command and insert it as is
392 qCDebug(TEMPLATEPARSER_LOG) << "Command: TEXTPIPE=";
393 QString q;
394 const int len = parseQuotes(QStringLiteral("TEXTPIPE="), cmd, q);
395 i += len;
396 const QString pipe_cmd = q;
397 if (d->mOrigMsg) {
398 const QString plainStr = pipe(pipe_cmd, plainMessageText(shouldStripSignature(), NoSelectionAllowed));
399 plainBody.append(plainStr);
400
401 const QString htmlStr = pipe(pipe_cmd, htmlMessageText(shouldStripSignature(), NoSelectionAllowed));
402 htmlBody.append(htmlStr);
403 }
404 } else if (cmd.startsWith(QLatin1StringView("MSGPIPE="))) {
405 // pipe full message through command and insert result as is
406 qCDebug(TEMPLATEPARSER_LOG) << "Command: MSGPIPE=";
407 QString q;
408 const int len = parseQuotes(QStringLiteral("MSGPIPE="), cmd, q);
409 i += len;
410 if (d->mOrigMsg) {
411 const QString str = pipe(q, QString::fromLatin1(d->mOrigMsg->encodedContent()));
412 plainBody.append(str);
413
414 const QString body = plainTextToHtml(str);
415 htmlBody.append(body);
416 }
417 } else if (cmd.startsWith(QLatin1StringView("BODYPIPE="))) {
418 // pipe message body generated so far through command and insert result as is
419 qCDebug(TEMPLATEPARSER_LOG) << "Command: BODYPIPE=";
420 QString q;
421 const int len = parseQuotes(QStringLiteral("BODYPIPE="), cmd, q);
422 i += len;
423 const QString pipe_cmd = q;
424 const QString plainStr = pipe(q, plainBody);
425 plainBody.append(plainStr);
426
427 const QString htmlStr = pipe(pipe_cmd, htmlBody);
428 const QString body = plainTextToHtml(htmlStr);
429 htmlBody.append(body);
430 } else if (cmd.startsWith(QLatin1StringView("CLEARPIPE="))) {
431 // pipe message body generated so far through command and
432 // insert result as is replacing current body
433 qCDebug(TEMPLATEPARSER_LOG) << "Command: CLEARPIPE=";
434 QString q;
435 const int len = parseQuotes(QStringLiteral("CLEARPIPE="), cmd, q);
436 i += len;
437 const QString pipe_cmd = q;
438 const QString plainStr = pipe(pipe_cmd, plainBody);
439 plainBody = plainStr;
440
441 const QString htmlStr = pipe(pipe_cmd, htmlBody);
442 htmlBody = htmlStr;
443
444 auto header = new KMime::Headers::Generic("X-KMail-CursorPos");
445 header->fromUnicodeString(QString::number(0));
446 d->mMsg->setHeader(header);
447 } else if (cmd.startsWith(QLatin1StringView("TEXT"))) {
448 qCDebug(TEMPLATEPARSER_LOG) << "Command: TEXT";
449 i += strlen("TEXT");
450 if (d->mOrigMsg) {
451 const QString plainStr = plainMessageText(shouldStripSignature(), NoSelectionAllowed);
452 plainBody.append(plainStr);
453
454 const QString htmlStr = htmlMessageText(shouldStripSignature(), NoSelectionAllowed);
455 htmlBody.append(htmlStr);
456 }
457 } else if (cmd.startsWith(QLatin1StringView("OTEXTSIZE"))) {
458 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTEXTSIZE";
459 i += strlen("OTEXTSIZE");
460 if (d->mOrigMsg) {
461 const QString str = QStringLiteral("%1").arg(d->mOrigMsg->body().length());
462 plainBody.append(str);
463 const QString body = plainTextToHtml(str);
464 htmlBody.append(body);
465 }
466 } else if (cmd.startsWith(QLatin1StringView("OTEXT"))) {
467 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTEXT";
468 i += strlen("OTEXT");
469 if (d->mOrigMsg) {
470 const QString plainStr = plainMessageText(shouldStripSignature(), NoSelectionAllowed);
471 plainBody.append(plainStr);
472
473 const QString htmlStr = htmlMessageText(shouldStripSignature(), NoSelectionAllowed);
474 htmlBody.append(htmlStr);
475 }
476 } else if (cmd.startsWith(QLatin1StringView("OADDRESSEESADDR"))) {
477 qCDebug(TEMPLATEPARSER_LOG) << "Command: OADDRESSEESADDR";
478 i += strlen("OADDRESSEESADDR");
479 if (d->mOrigMsg) {
480 const QString to = d->mOrigMsg->to()->asUnicodeString();
481 const QString cc = d->mOrigMsg->cc()->asUnicodeString();
482 if (!to.isEmpty()) {
483 const QString toLine = i18nc("@item:intext email To", "To:") + QLatin1Char(' ') + to;
484 plainBody.append(toLine);
485 const QString body = plainTextToHtml(toLine);
486 htmlBody.append(body);
487 }
488 if (!to.isEmpty() && !cc.isEmpty()) {
489 plainBody.append(QLatin1Char('\n'));
490 const QString str = plainTextToHtml(QString(QLatin1Char('\n')));
491 htmlBody.append(str);
492 }
493 if (!cc.isEmpty()) {
494 const QString ccLine = i18nc("@item:intext email CC", "CC:") + QLatin1Char(' ') + cc;
495 plainBody.append(ccLine);
496 const QString str = plainTextToHtml(ccLine);
497 htmlBody.append(str);
498 }
499 }
500 } else if (cmd.startsWith(QLatin1StringView("CCADDR"))) {
501 qCDebug(TEMPLATEPARSER_LOG) << "Command: CCADDR";
502 i += strlen("CCADDR");
503 const QString str = d->mMsg->cc()->asUnicodeString();
504 plainBody.append(str);
505 const QString body = plainTextToHtml(str);
506 htmlBody.append(body);
507 } else if (cmd.startsWith(QLatin1StringView("CCNAME"))) {
508 qCDebug(TEMPLATEPARSER_LOG) << "Command: CCNAME";
509 i += strlen("CCNAME");
510 const QString str = d->mMsg->cc()->displayString();
511 plainBody.append(str);
512 const QString body = plainTextToHtml(str);
513 htmlBody.append(body);
514 } else if (cmd.startsWith(QLatin1StringView("CCFNAME"))) {
515 qCDebug(TEMPLATEPARSER_LOG) << "Command: CCFNAME";
516 i += strlen("CCFNAME");
517 const QString str = d->mMsg->cc()->displayString();
518 const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str);
519 plainBody.append(firstNameFromEmail);
520 const QString body = plainTextToHtml(firstNameFromEmail);
521 htmlBody.append(body);
522 } else if (cmd.startsWith(QLatin1StringView("CCLNAME"))) {
523 qCDebug(TEMPLATEPARSER_LOG) << "Command: CCLNAME";
524 i += strlen("CCLNAME");
525 const QString str = d->mMsg->cc()->displayString();
526 plainBody.append(TemplateParser::Util::getLastNameFromEmail(str));
527 const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str));
528 htmlBody.append(body);
529 } else if (cmd.startsWith(QLatin1StringView("TOADDR"))) {
530 qCDebug(TEMPLATEPARSER_LOG) << "Command: TOADDR";
531 i += strlen("TOADDR");
532 const QString str = d->mMsg->to()->asUnicodeString();
533 plainBody.append(str);
534 const QString body = plainTextToHtml(str);
535 htmlBody.append(body);
536 } else if (cmd.startsWith(QLatin1StringView("TONAME"))) {
537 qCDebug(TEMPLATEPARSER_LOG) << "Command: TONAME";
538 i += strlen("TONAME");
539 const QString str = (d->mMsg->to()->displayString());
540 plainBody.append(str);
541 const QString body = plainTextToHtml(str);
542 htmlBody.append(body);
543 } else if (cmd.startsWith(QLatin1StringView("TOFNAME"))) {
544 qCDebug(TEMPLATEPARSER_LOG) << "Command: TOFNAME";
545 i += strlen("TOFNAME");
546 const QString str = d->mMsg->to()->displayString();
547 const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str);
548 plainBody.append(firstNameFromEmail);
549 const QString body = plainTextToHtml(firstNameFromEmail);
550 htmlBody.append(body);
551 } else if (cmd.startsWith(QLatin1StringView("TOLNAME"))) {
552 qCDebug(TEMPLATEPARSER_LOG) << "Command: TOLNAME";
553 i += strlen("TOLNAME");
554 const QString str = d->mMsg->to()->displayString();
555 plainBody.append(TemplateParser::Util::getLastNameFromEmail(str));
556 const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str));
557 htmlBody.append(body);
558 } else if (cmd.startsWith(QLatin1StringView("TOLIST"))) {
559 qCDebug(TEMPLATEPARSER_LOG) << "Command: TOLIST";
560 i += strlen("TOLIST");
561 const QString str = d->mMsg->to()->asUnicodeString();
562 plainBody.append(str);
563 const QString body = plainTextToHtml(str);
564 htmlBody.append(body);
565 } else if (cmd.startsWith(QLatin1StringView("FROMADDR"))) {
566 qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMADDR";
567 i += strlen("FROMADDR");
568 const QString str = d->mMsg->from()->asUnicodeString();
569 plainBody.append(str);
570 const QString body = plainTextToHtml(str);
571 htmlBody.append(body);
572 } else if (cmd.startsWith(QLatin1StringView("FROMNAME"))) {
573 qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMNAME";
574 i += strlen("FROMNAME");
575 const QString str = d->mMsg->from()->displayString();
576 plainBody.append(str);
577 const QString body = plainTextToHtml(str);
578 htmlBody.append(body);
579 } else if (cmd.startsWith(QLatin1StringView("FROMFNAME"))) {
580 qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMFNAME";
581 i += strlen("FROMFNAME");
582 const QString str = d->mMsg->from()->displayString();
583 const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str);
584 plainBody.append(firstNameFromEmail);
585 const QString body = plainTextToHtml(firstNameFromEmail);
586 htmlBody.append(body);
587 } else if (cmd.startsWith(QLatin1StringView("FROMLNAME"))) {
588 qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMLNAME";
589 i += strlen("FROMLNAME");
590 const QString str = d->mMsg->from()->displayString();
591 plainBody.append(TemplateParser::Util::getLastNameFromEmail(str));
592 const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str));
593 htmlBody.append(body);
594 } else if (cmd.startsWith(QLatin1StringView("FULLSUBJECT")) || cmd.startsWith(QLatin1StringView("FULLSUBJ"))) {
595 if (cmd.startsWith(QLatin1StringView("FULLSUBJ"))) {
596 qCDebug(TEMPLATEPARSER_LOG) << "Command: FULLSUBJ";
597 i += strlen("FULLSUBJ");
598 } else {
599 qCDebug(TEMPLATEPARSER_LOG) << "Command: FULLSUBJECT";
600 i += strlen("FULLSUBJECT");
601 }
602 const QString str = d->mMsg->subject()->asUnicodeString();
603 plainBody.append(str);
604 const QString body = plainTextToHtml(str);
605 htmlBody.append(body);
606 } else if (cmd.startsWith(QLatin1StringView("MSGID"))) {
607 qCDebug(TEMPLATEPARSER_LOG) << "Command: MSGID";
608 i += strlen("MSGID");
609 const QString str = d->mMsg->messageID()->asUnicodeString();
610 plainBody.append(str);
611 const QString body = plainTextToHtml(str);
612 htmlBody.append(body);
613 } else if (cmd.startsWith(QLatin1StringView("OHEADER="))) {
614 // insert specified content of header from original message
615 qCDebug(TEMPLATEPARSER_LOG) << "Command: OHEADER=";
616 QString q;
617 const int len = parseQuotes(QStringLiteral("OHEADER="), cmd, q);
618 i += len;
619 if (d->mOrigMsg) {
620 const QString hdr = q;
621 QString str;
622 if (auto hrdMsgOrigin = d->mOrigMsg->headerByType(hdr.toLocal8Bit().constData())) {
623 str = hrdMsgOrigin->asUnicodeString();
624 }
625 plainBody.append(str);
626 const QString body = plainTextToHtml(str);
627 htmlBody.append(body);
628 }
629 } else if (cmd.startsWith(QLatin1StringView("HEADER="))) {
630 // insert specified content of header from current message
631 qCDebug(TEMPLATEPARSER_LOG) << "Command: HEADER=";
632 QString q;
633 const int len = parseQuotes(QStringLiteral("HEADER="), cmd, q);
634 i += len;
635 const QString hdr = q;
636 QString str;
637 if (auto hrdMsgOrigin = d->mOrigMsg->headerByType(hdr.toLocal8Bit().constData())) {
638 str = hrdMsgOrigin->asUnicodeString();
639 }
640 plainBody.append(str);
641 const QString body = plainTextToHtml(str);
642 htmlBody.append(body);
643 } else if (cmd.startsWith(QLatin1StringView("HEADER( "))) {
644 // insert specified content of header from current message
645 qCDebug(TEMPLATEPARSER_LOG) << "Command: HEADER(";
647 static QRegularExpression reg(QStringLiteral("^HEADER\\((.+)\\)"));
648 const int res = cmd.indexOf(reg, 0, &match);
649 if (res != 0) {
650 // something wrong
651 i += strlen("HEADER( ");
652 } else {
653 i += match.capturedLength(0); // length of HEADER(<space> + <space>)
654 const QString hdr = match.captured(1).trimmed();
655 QString str;
656 if (auto hrdMsgOrigin = d->mOrigMsg->headerByType(hdr.toLocal8Bit().constData())) {
657 str = hrdMsgOrigin->asUnicodeString();
658 }
659 plainBody.append(str);
660 const QString body = plainTextToHtml(str);
661 htmlBody.append(body);
662 }
663 } else if (cmd.startsWith(QLatin1StringView("OCCADDR"))) {
664 qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCADDR";
665 i += strlen("OCCADDR");
666 if (d->mOrigMsg) {
667 const QString str = d->mOrigMsg->cc()->asUnicodeString();
668 plainBody.append(str);
669 const QString body = plainTextToHtml(str);
670 htmlBody.append(body);
671 }
672 } else if (cmd.startsWith(QLatin1StringView("OCCNAME"))) {
673 qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCNAME";
674 i += strlen("OCCNAME");
675 if (d->mOrigMsg) {
676 const QString str = d->mOrigMsg->cc()->displayString();
677 plainBody.append(str);
678 const QString body = plainTextToHtml(str);
679 htmlBody.append(body);
680 }
681 } else if (cmd.startsWith(QLatin1StringView("OCCFNAME"))) {
682 qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCFNAME";
683 i += strlen("OCCFNAME");
684 if (d->mOrigMsg) {
685 const QString str = d->mOrigMsg->cc()->displayString();
686 const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str);
687 plainBody.append(firstNameFromEmail);
688 const QString body = plainTextToHtml(firstNameFromEmail);
689 htmlBody.append(body);
690 }
691 } else if (cmd.startsWith(QLatin1StringView("OCCLNAME"))) {
692 qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCLNAME";
693 i += strlen("OCCLNAME");
694 if (d->mOrigMsg) {
695 const QString str = d->mOrigMsg->cc()->displayString();
696 plainBody.append(TemplateParser::Util::getLastNameFromEmail(str));
697 const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str));
698 htmlBody.append(body);
699 }
700 } else if (cmd.startsWith(QLatin1StringView("OTOADDR"))) {
701 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOADDR";
702 i += strlen("OTOADDR");
703 if (d->mOrigMsg) {
704 const QString str = d->mOrigMsg->to()->asUnicodeString();
705 plainBody.append(str);
706 const QString body = plainTextToHtml(str);
707 htmlBody.append(body);
708 }
709 } else if (cmd.startsWith(QLatin1StringView("OTONAME"))) {
710 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTONAME";
711 i += strlen("OTONAME");
712 if (d->mOrigMsg) {
713 const QString str = d->mOrigMsg->to()->displayString();
714 plainBody.append(str);
715 const QString body = plainTextToHtml(str);
716 htmlBody.append(body);
717 }
718 } else if (cmd.startsWith(QLatin1StringView("OTOFNAME"))) {
719 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOFNAME";
720 i += strlen("OTOFNAME");
721 if (d->mOrigMsg) {
722 const QString str = d->mOrigMsg->to()->displayString();
723 const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str);
724 plainBody.append(firstNameFromEmail);
725 const QString body = plainTextToHtml(firstNameFromEmail);
726 htmlBody.append(body);
727 }
728 } else if (cmd.startsWith(QLatin1StringView("OTOLNAME"))) {
729 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOLNAME";
730 i += strlen("OTOLNAME");
731 if (d->mOrigMsg) {
732 const QString str = d->mOrigMsg->to()->displayString();
733 plainBody.append(TemplateParser::Util::getLastNameFromEmail(str));
734 const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str));
735 htmlBody.append(body);
736 }
737 } else if (cmd.startsWith(QLatin1StringView("OTOLIST"))) {
738 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOLIST";
739 i += strlen("OTOLIST");
740 if (d->mOrigMsg) {
741 const QString str = d->mOrigMsg->to()->asUnicodeString();
742 plainBody.append(str);
743 const QString body = plainTextToHtml(str);
744 htmlBody.append(body);
745 }
746 } else if (cmd.startsWith(QLatin1StringView("OTO"))) {
747 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTO";
748 i += strlen("OTO");
749 if (d->mOrigMsg) {
750 const QString str = d->mOrigMsg->to()->asUnicodeString();
751 plainBody.append(str);
752 const QString body = plainTextToHtml(str);
753 htmlBody.append(body);
754 }
755 } else if (cmd.startsWith(QLatin1StringView("OFROMADDR"))) {
756 qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMADDR";
757 i += strlen("OFROMADDR");
758 if (d->mOrigMsg) {
759 const QString str = d->mOrigMsg->from()->asUnicodeString();
760 plainBody.append(str);
761 const QString body = plainTextToHtml(str);
762 htmlBody.append(body);
763 }
764 } else if (cmd.startsWith(QLatin1StringView("OFROMNAME"))) {
765 qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMNAME";
766 i += strlen("OFROMNAME");
767 if (d->mOrigMsg) {
768 const QString str = d->mOrigMsg->from()->displayString();
769 plainBody.append(str);
770 const QString body = plainTextToHtml(str);
771 htmlBody.append(body);
772 }
773 } else if (cmd.startsWith(QLatin1StringView("OFROMFNAME"))) {
774 qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMFNAME";
775 i += strlen("OFROMFNAME");
776 if (d->mOrigMsg) {
777 const QString str = d->mOrigMsg->from()->displayString();
778 const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str);
779 plainBody.append(firstNameFromEmail);
780 const QString body = plainTextToHtml(firstNameFromEmail);
781 htmlBody.append(body);
782 }
783 } else if (cmd.startsWith(QLatin1StringView("OFROMLNAME"))) {
784 qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMLNAME";
785 i += strlen("OFROMLNAME");
786 if (d->mOrigMsg) {
787 const QString str = d->mOrigMsg->from()->displayString();
788 plainBody.append(TemplateParser::Util::getLastNameFromEmail(str));
789 const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str));
790 htmlBody.append(body);
791 }
792 } else if (cmd.startsWith(QLatin1StringView("OFULLSUBJECT")) || cmd.startsWith(QLatin1StringView("OFULLSUBJ"))) {
793 if (cmd.startsWith(QLatin1StringView("OFULLSUBJECT"))) {
794 qCDebug(TEMPLATEPARSER_LOG) << "Command: OFULLSUBJECT";
795 i += strlen("OFULLSUBJECT");
796 } else {
797 qCDebug(TEMPLATEPARSER_LOG) << "Command: OFULLSUBJ";
798 i += strlen("OFULLSUBJ");
799 }
800 if (d->mOrigMsg) {
801 const QString str = d->mOrigMsg->subject()->asUnicodeString();
802 plainBody.append(str);
803 const QString body = plainTextToHtml(str);
804 htmlBody.append(body);
805 }
806 } else if (cmd.startsWith(QLatin1StringView("OMSGID"))) {
807 qCDebug(TEMPLATEPARSER_LOG) << "Command: OMSGID";
808 i += strlen("OMSGID");
809 if (d->mOrigMsg) {
810 const QString str = d->mOrigMsg->messageID()->asUnicodeString();
811 plainBody.append(str);
812 const QString body = plainTextToHtml(str);
813 htmlBody.append(body);
814 }
815 } else if (cmd.startsWith(QLatin1StringView("DATEEN"))) {
816 qCDebug(TEMPLATEPARSER_LOG) << "Command: DATEEN";
817 i += strlen("DATEEN");
819 QLocale locale(QLocale::C);
820 const QString str = locale.toString(date.date(), QLocale::LongFormat);
821 plainBody.append(str);
822 const QString body = plainTextToHtml(str);
823 htmlBody.append(body);
824 } else if (cmd.startsWith(QLatin1StringView("DATESHORT"))) {
825 qCDebug(TEMPLATEPARSER_LOG) << "Command: DATESHORT";
826 i += strlen("DATESHORT");
828 const QString str = definedLocale.toString(date.date(), QLocale::ShortFormat);
829 plainBody.append(str);
830 const QString body = plainTextToHtml(str);
831 htmlBody.append(body);
832 } else if (cmd.startsWith(QLatin1StringView("DATE"))) {
833 qCDebug(TEMPLATEPARSER_LOG) << "Command: DATE";
834 i += strlen("DATE");
836 const QString str = definedLocale.toString(date.date(), QLocale::LongFormat);
837 plainBody.append(str);
838 const QString body = plainTextToHtml(str);
839 htmlBody.append(body);
840 } else if (cmd.startsWith(QLatin1StringView("DOW"))) {
841 qCDebug(TEMPLATEPARSER_LOG) << "Command: DOW";
842 i += strlen("DOW");
844 const QString str = definedLocale.dayName(date.date().dayOfWeek(), QLocale::LongFormat);
845 plainBody.append(str);
846 const QString body = plainTextToHtml(str);
847 htmlBody.append(body);
848 } else if (cmd.startsWith(QLatin1StringView("TIMELONGEN"))) {
849 qCDebug(TEMPLATEPARSER_LOG) << "Command: TIMELONGEN";
850 i += strlen("TIMELONGEN");
852 QLocale locale(QLocale::C);
853 const QString str = locale.toString(date.time(), QLocale::LongFormat);
854 plainBody.append(str);
855 const QString body = plainTextToHtml(str);
856 htmlBody.append(body);
857 } else if (cmd.startsWith(QLatin1StringView("TIMELONG"))) {
858 qCDebug(TEMPLATEPARSER_LOG) << "Command: TIMELONG";
859 i += strlen("TIMELONG");
861 const QString str = definedLocale.toString(date.time(), QLocale::LongFormat);
862 plainBody.append(str);
863 const QString body = plainTextToHtml(str);
864 htmlBody.append(body);
865 } else if (cmd.startsWith(QLatin1StringView("TIME"))) {
866 qCDebug(TEMPLATEPARSER_LOG) << "Command: TIME";
867 i += strlen("TIME");
869 const QString str = definedLocale.toString(date.time(), QLocale::ShortFormat);
870 plainBody.append(str);
871 const QString body = plainTextToHtml(str);
872 htmlBody.append(body);
873 } else if (cmd.startsWith(QLatin1StringView("ODATEEN"))) {
874 qCDebug(TEMPLATEPARSER_LOG) << "Command: ODATEEN";
875 i += strlen("ODATEEN");
876 if (d->mOrigMsg) {
877 const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
878 const QString str = QLocale::c().toString(date.date(), QLocale::LongFormat);
879 plainBody.append(str);
880 const QString body = plainTextToHtml(str);
881 htmlBody.append(body);
882 }
883 } else if (cmd.startsWith(QLatin1StringView("ODATESHORT"))) {
884 qCDebug(TEMPLATEPARSER_LOG) << "Command: ODATESHORT";
885 i += strlen("ODATESHORT");
886 if (d->mOrigMsg) {
887 const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
888 const QString str = definedLocale.toString(date.date(), QLocale::ShortFormat);
889 plainBody.append(str);
890 const QString body = plainTextToHtml(str);
891 htmlBody.append(body);
892 }
893 } else if (cmd.startsWith(QLatin1StringView("ODATE"))) {
894 qCDebug(TEMPLATEPARSER_LOG) << "Command: ODATE";
895 i += strlen("ODATE");
896 if (d->mOrigMsg) {
897 const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
898 const QString str = definedLocale.toString(date.date(), QLocale::LongFormat);
899 plainBody.append(str);
900 const QString body = plainTextToHtml(str);
901 htmlBody.append(body);
902 }
903 } else if (cmd.startsWith(QLatin1StringView("ODOW"))) {
904 qCDebug(TEMPLATEPARSER_LOG) << "Command: ODOW";
905 i += strlen("ODOW");
906 if (d->mOrigMsg) {
907 const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
908 const QString str = definedLocale.dayName(date.date().dayOfWeek(), QLocale::LongFormat);
909 plainBody.append(str);
910 const QString body = plainTextToHtml(str);
911 htmlBody.append(body);
912 }
913 } else if (cmd.startsWith(QLatin1StringView("OTIMELONGEN"))) {
914 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTIMELONGEN";
915 i += strlen("OTIMELONGEN");
916 if (d->mOrigMsg) {
917 const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
918 QLocale locale(QLocale::C);
919 const QString str = locale.toString(date.time(), QLocale::LongFormat);
920 plainBody.append(str);
921 const QString body = plainTextToHtml(str);
922 htmlBody.append(body);
923 }
924 } else if (cmd.startsWith(QLatin1StringView("OTIMELONG"))) {
925 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTIMELONG";
926 i += strlen("OTIMELONG");
927 if (d->mOrigMsg) {
928 const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
929 const QString str = definedLocale.toString(date.time(), QLocale::LongFormat);
930 plainBody.append(str);
931 const QString body = plainTextToHtml(str);
932 htmlBody.append(body);
933 }
934 } else if (cmd.startsWith(QLatin1StringView("OTIME"))) {
935 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTIME";
936 i += strlen("OTIME");
937 if (d->mOrigMsg) {
938 const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
939 const QString str = definedLocale.toString(date.time(), QLocale::ShortFormat);
940 plainBody.append(str);
941 const QString body = plainTextToHtml(str);
942 htmlBody.append(body);
943 }
944 } else if (cmd.startsWith(QLatin1StringView("BLANK"))) {
945 // do nothing
946 qCDebug(TEMPLATEPARSER_LOG) << "Command: BLANK";
947 i += strlen("BLANK");
948 } else if (cmd.startsWith(QLatin1StringView("NOP"))) {
949 // do nothing
950 qCDebug(TEMPLATEPARSER_LOG) << "Command: NOP";
951 i += strlen("NOP");
952 } else if (cmd.startsWith(QLatin1StringView("CLEAR"))) {
953 // clear body buffer; not too useful yet
954 qCDebug(TEMPLATEPARSER_LOG) << "Command: CLEAR";
955 i += strlen("CLEAR");
956 plainBody.clear();
957 htmlBody.clear();
958 auto header = new KMime::Headers::Generic("X-KMail-CursorPos");
959 header->fromUnicodeString(QString::number(0));
960 d->mMsg->setHeader(header);
961 } else if (cmd.startsWith(QLatin1StringView("DEBUGOFF"))) {
962 // turn off debug
963 qCDebug(TEMPLATEPARSER_LOG) << "Command: DEBUGOFF";
964 i += strlen("DEBUGOFF");
965 d->mDebug = false;
966 } else if (cmd.startsWith(QLatin1StringView("DEBUG"))) {
967 // turn on debug
968 qCDebug(TEMPLATEPARSER_LOG) << "Command: DEBUG";
969 i += strlen("DEBUG");
970 d->mDebug = true;
971 } else if (cmd.startsWith(QLatin1StringView("CURSOR"))) {
972 // turn on debug
973 qCDebug(TEMPLATEPARSER_LOG) << "Command: CURSOR";
974 int oldI = i;
975 i += strlen("CURSOR");
976 auto header = new KMime::Headers::Generic("X-KMail-CursorPos");
977 header->fromUnicodeString(QString::number(plainBody.length()));
978 /* if template is:
979 * FOOBAR
980 * %CURSOR
981 *
982 * Make sure there is an empty line for the cursor otherwise it will be placed at the end of FOOBAR
983 */
984 if (oldI > 0 && tmpl[oldI - 1] == QLatin1Char('\n') && i == tmpl_len - 1) {
985 plainBody.append(QLatin1Char('\n'));
986 }
987 d->mMsg->setHeader(header);
988 d->mForceCursorPosition = true;
989 // FIXME HTML part for header remaining
990 } else if (cmd.startsWith(QLatin1StringView("SIGNATURE"))) {
991 qCDebug(TEMPLATEPARSER_LOG) << "Command: SIGNATURE";
992 i += strlen("SIGNATURE");
993 plainBody.append(getPlainSignature());
994 htmlBody.append(getHtmlSignature());
995 } else {
996 // wrong command, do nothing
997 plainBody.append(c);
998 htmlBody.append(c);
999 }
1000 } else if (dnl && (c == QLatin1Char('\n') || c == QLatin1Char('\r'))) {
1001 // skip
1002 if ((tmpl.size() > i + 1)
1003 && ((c == QLatin1Char('\n') && tmpl[i + 1] == QLatin1Char('\r')) || (c == QLatin1Char('\r') && tmpl[i + 1] == QLatin1Char('\n')))) {
1004 // skip one more
1005 i += 1;
1006 }
1007 dnl = false;
1008 } else {
1009 plainBody.append(c);
1010 if (c == QLatin1Char('\n') || c == QLatin1Char('\r')) {
1011 htmlBody.append(QLatin1StringView("<br />"));
1012 htmlBody.append(c);
1013 if (tmpl.size() > i + 1
1014 && ((c == QLatin1Char('\n') && tmpl[i + 1] == QLatin1Char('\r')) || (c == QLatin1Char('\r') && tmpl[i + 1] == QLatin1Char('\n')))) {
1015 htmlBody.append(tmpl[i + 1]);
1016 plainBody.append(tmpl[i + 1]);
1017 i += 1;
1018 }
1019 } else {
1020 htmlBody.append(c);
1021 }
1022 }
1023 }
1024 // Clear the HTML body if FORCEDPLAIN has set ReplyAsPlain, OR if,
1025 // there is no use of FORCED command but a configure setting has ReplyUsingHtml disabled,
1026 // OR the original mail has no HTML part.
1027 const KMime::Content *content = d->mOrigMsg->mainBodyPart("text/html");
1028 if (d->mQuotes == ReplyAsPlain || (!d->mReplyAsHtml && TemplateParserSettings::self()->replyUsingVisualFormat())
1029 || !TemplateParserSettings::self()->replyUsingVisualFormat() || (!content || !content->hasContent())) {
1030 htmlBody.clear();
1031 } else {
1032 // qDebug() << "htmlBody********************* " << htmlBody;
1033 makeValidHtml(htmlBody);
1034 }
1035 if (d->mMode == NewMessage && plainBody.isEmpty() && !d->mExtractHtmlInfoResult.mPlainText.isEmpty()) {
1036 plainBody = d->mExtractHtmlInfoResult.mPlainText;
1037 }
1038
1039 addProcessedBodyToMessage(plainBody, htmlBody);
1040 Q_EMIT parsingDone(d->mForceCursorPosition);
1041 deleteLater();
1042}
1043
1044QString TemplateParserJob::getPlainSignature() const
1045{
1046 const KIdentityManagementCore::Identity &identity = d->m_identityManager->identityForUoid(d->mIdentity);
1047
1048 if (identity.isNull()) {
1049 return {};
1050 }
1051
1052 KIdentityManagementCore::Signature signature = const_cast<KIdentityManagementCore::Identity &>(identity).signature();
1053
1054 if (signature.type() == KIdentityManagementCore::Signature::Inlined && signature.isInlinedHtml()) {
1055 return signature.toPlainText();
1056 } else {
1057 return signature.rawText();
1058 }
1059}
1060
1061// TODO If %SIGNATURE command is on, then override it with signature from
1062// "KMail configure->General->identity->signature".
1063// There should be no two signatures.
1064QString TemplateParserJob::getHtmlSignature() const
1065{
1066 const KIdentityManagementCore::Identity &identity = d->m_identityManager->identityForUoid(d->mIdentity);
1067 if (identity.isNull()) {
1068 return {};
1069 }
1070
1071 KIdentityManagementCore::Signature signature = const_cast<KIdentityManagementCore::Identity &>(identity).signature();
1072
1073 if (!signature.isInlinedHtml()) {
1074 signature = signature.rawText().toHtmlEscaped();
1075 return signature.rawText().replace(QLatin1Char('\n'), QStringLiteral("<br />"));
1076 }
1077 return signature.rawText();
1078}
1079
1080void TemplateParserJob::addProcessedBodyToMessage(const QString &plainBody, const QString &htmlBody) const
1081{
1083 ic.collectImagesFrom(d->mOrigMsg.data());
1084
1085 // Now, delete the old content and set the new content, which
1086 // is either only the new text or the new text with some attachments.
1087 const auto parts = d->mMsg->contents();
1088 for (KMime::Content *content : parts) {
1089 auto c = d->mMsg->takeContent(content);
1090 delete c;
1091 }
1092
1093 // Set To and CC from the template
1094 if (!d->mTo.isEmpty()) {
1095 d->mMsg->to()->fromUnicodeString(d->mMsg->to()->asUnicodeString() + QLatin1Char(',') + d->mTo);
1096 }
1097
1098 if (!d->mCC.isEmpty()) {
1099 d->mMsg->cc()->fromUnicodeString(d->mMsg->cc()->asUnicodeString() + QLatin1Char(',') + d->mCC);
1100 }
1101
1102 d->mMsg->removeHeader<KMime::Headers::ContentType>(); // to get rid of old boundary
1103
1104 // const QByteArray boundary = KMime::multiPartBoundary();
1105 KMime::Content *const mainTextPart = htmlBody.isEmpty() ? createPlainPartContent(plainBody) : createMultipartAlternativeContent(plainBody, htmlBody);
1106 mainTextPart->assemble();
1107
1108 KMime::Content *textPart = mainTextPart;
1109 if (!ic.images().empty()) {
1110 textPart = createMultipartRelated(ic, mainTextPart);
1111 textPart->assemble();
1112 }
1113
1114 // If we have some attachments, create a multipart/mixed mail and
1115 // add the normal body as well as the attachments
1116 KMime::Content *mainPart = textPart;
1117 if (d->mMode == Forward) {
1118 auto attachments = d->mOrigMsg->attachments();
1119 attachments += d->mOtp->nodeHelper()->attachmentsOfExtraContents();
1120 if (!attachments.isEmpty()) {
1121 mainPart = createMultipartMixed(attachments, textPart);
1122 mainPart->assemble();
1123 }
1124 }
1125
1126 d->mMsg->setBody(mainPart->encodedBody());
1127 d->mMsg->setHeader(mainPart->contentType());
1128 d->mMsg->setHeader(mainPart->contentTransferEncoding());
1129 d->mMsg->assemble();
1130 d->mMsg->parse();
1131}
1132
1133KMime::Content *TemplateParserJob::createMultipartMixed(const QList<KMime::Content *> &attachments, KMime::Content *textPart) const
1134{
1135 auto mixedPart = new KMime::Content(d->mMsg.data());
1136 const QByteArray boundary = KMime::multiPartBoundary();
1137 auto contentType = mixedPart->contentType();
1138 contentType->setMimeType("multipart/mixed");
1139 contentType->setBoundary(boundary);
1140 mixedPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
1141 mixedPart->appendContent(textPart);
1142
1143 int attachmentNumber = 1;
1144 for (KMime::Content *attachment : attachments) {
1145 mixedPart->appendContent(attachment);
1146 // If the content type has no name or filename parameter, add one, since otherwise the name
1147 // would be empty in the attachment view of the composer, which looks confusing
1148 if (auto ct = attachment->contentType(false)) {
1149 if (!ct->hasParameter("name") && !ct->hasParameter("filename")) {
1150 ct->setParameter(QByteArrayLiteral("name"), i18nc("@item:intext", "Attachment %1", attachmentNumber));
1151 }
1152 }
1153 ++attachmentNumber;
1154 }
1155 return mixedPart;
1156}
1157
1158KMime::Content *TemplateParserJob::createMultipartRelated(const MessageCore::ImageCollector &ic, KMime::Content *mainTextPart) const
1159{
1160 auto relatedPart = new KMime::Content(d->mMsg.data());
1161 const QByteArray boundary = KMime::multiPartBoundary();
1162 auto contentType = relatedPart->contentType();
1163 contentType->setMimeType("multipart/related");
1164 contentType->setBoundary(boundary);
1165 relatedPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
1166 relatedPart->appendContent(mainTextPart);
1167 for (KMime::Content *image : ic.images()) {
1168 qCWarning(TEMPLATEPARSER_LOG) << "Adding" << image->contentID() << "as an embedded image";
1169 relatedPart->appendContent(image);
1170 }
1171 return relatedPart;
1172}
1173
1174KMime::Content *TemplateParserJob::createPlainPartContent(const QString &plainBody) const
1175{
1176 auto textPart = new KMime::Content(d->mMsg.data());
1177 auto ct = textPart->contentType(true);
1178 ct->setMimeType("text/plain");
1179 ct->setCharset(QByteArrayLiteral("UTF-8"));
1180 textPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE8Bit);
1181 textPart->fromUnicodeString(plainBody);
1182 return textPart;
1183}
1184
1185KMime::Content *TemplateParserJob::createMultipartAlternativeContent(const QString &plainBody, const QString &htmlBody) const
1186{
1187 auto multipartAlternative = new KMime::Content(d->mMsg.data());
1188 multipartAlternative->contentType()->setMimeType("multipart/alternative");
1189 const QByteArray boundary = KMime::multiPartBoundary();
1190 multipartAlternative->contentType(false)->setBoundary(boundary); // Already created
1191
1192 KMime::Content *textPart = createPlainPartContent(plainBody);
1193 multipartAlternative->appendContent(textPart);
1194
1195 auto htmlPart = new KMime::Content(d->mMsg.data());
1196 htmlPart->contentType(true)->setMimeType("text/html");
1197 htmlPart->contentType(false)->setCharset(QByteArrayLiteral("UTF-8")); // Already created
1198 htmlPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE8Bit);
1199 htmlPart->fromUnicodeString(htmlBody);
1200 multipartAlternative->appendContent(htmlPart);
1201
1202 return multipartAlternative;
1203}
1204
1205QString TemplateParserJob::findCustomTemplate(const QString &tmplName)
1206{
1207 CTemplates t(tmplName);
1208 d->mTo = t.to();
1209 d->mCC = t.cC();
1210 const QString content = t.content();
1211 if (!content.isEmpty()) {
1212 return content;
1213 } else {
1214 return findTemplate();
1215 }
1216}
1217
1218QString TemplateParserJob::findTemplate()
1219{
1220 // qCDebug(TEMPLATEPARSER_LOG) << "Trying to find template for mode" << mode;
1221
1222 QString tmpl;
1223
1224 qCDebug(TEMPLATEPARSER_LOG) << "Folder identify found:" << d->mFolder;
1225 if (d->mFolder >= 0) { // only if a folder was found
1226 QString fid = QString::number(d->mFolder);
1227 Templates fconf(fid);
1228 if (fconf.useCustomTemplates()) { // does folder use custom templates?
1229 switch (d->mMode) {
1230 case NewMessage:
1231 tmpl = fconf.templateNewMessage();
1232 break;
1233 case Reply:
1234 tmpl = fconf.templateReply();
1235 break;
1236 case ReplyAll:
1237 tmpl = fconf.templateReplyAll();
1238 break;
1239 case Forward:
1240 tmpl = fconf.templateForward();
1241 break;
1242 default:
1243 qCDebug(TEMPLATEPARSER_LOG) << "Unknown message mode:" << d->mMode;
1244 return {};
1245 }
1246 d->mQuoteString = fconf.quoteString();
1247 if (!tmpl.isEmpty()) {
1248 return tmpl; // use folder-specific template
1249 }
1250 }
1251 }
1252
1253 if (!d->mIdentity) { // find identity message belongs to
1254 d->mIdentity = identityUoid(d->mMsg);
1255 if (!d->mIdentity && d->mOrigMsg) {
1256 d->mIdentity = identityUoid(d->mOrigMsg);
1257 }
1258 d->mIdentity = d->m_identityManager->identityForUoidOrDefault(d->mIdentity).uoid();
1259 if (!d->mIdentity) {
1260 qCDebug(TEMPLATEPARSER_LOG) << "Oops! No identity for message";
1261 }
1262 }
1263 qCDebug(TEMPLATEPARSER_LOG) << "Identity found:" << d->mIdentity;
1264
1265 QString iid;
1266 if (d->mIdentity) {
1267 iid = TemplatesConfiguration::configIdString(d->mIdentity); // templates ID for that identity
1268 } else {
1269 iid = QStringLiteral("IDENTITY_NO_IDENTITY"); // templates ID for no identity
1270 }
1271
1272 Templates iconf(iid);
1273 if (iconf.useCustomTemplates()) { // does identity use custom templates?
1274 switch (d->mMode) {
1275 case NewMessage:
1276 tmpl = iconf.templateNewMessage();
1277 break;
1278 case Reply:
1279 tmpl = iconf.templateReply();
1280 break;
1281 case ReplyAll:
1282 tmpl = iconf.templateReplyAll();
1283 break;
1284 case Forward:
1285 tmpl = iconf.templateForward();
1286 break;
1287 default:
1288 qCDebug(TEMPLATEPARSER_LOG) << "Unknown message mode:" << d->mMode;
1289 return {};
1290 }
1291 d->mQuoteString = iconf.quoteString();
1292 if (!tmpl.isEmpty()) {
1293 return tmpl; // use identity-specific template
1294 }
1295 }
1296
1297 switch (d->mMode) { // use the global template
1298 case NewMessage:
1299 tmpl = TemplateParserSettings::self()->templateNewMessage();
1300 break;
1301 case Reply:
1302 tmpl = TemplateParserSettings::self()->templateReply();
1303 break;
1304 case ReplyAll:
1305 tmpl = TemplateParserSettings::self()->templateReplyAll();
1306 break;
1307 case Forward:
1308 tmpl = TemplateParserSettings::self()->templateForward();
1309 break;
1310 default:
1311 qCDebug(TEMPLATEPARSER_LOG) << "Unknown message mode:" << d->mMode;
1312 return {};
1313 }
1314
1315 d->mQuoteString = TemplateParserSettings::self()->quoteString();
1316 return tmpl;
1317}
1318
1319QString TemplateParserJob::pipe(const QString &cmd, const QString &buf)
1320{
1321 KProcess process;
1322 bool success;
1323
1325 process.setShellCommand(cmd);
1326 process.start();
1327 if (process.waitForStarted(pipeTimeout())) {
1328 bool finished = false;
1329 if (!buf.isEmpty()) {
1330 process.write(buf.toLatin1());
1331 }
1332 if (buf.isEmpty() || process.waitForBytesWritten(pipeTimeout())) {
1333 if (!buf.isEmpty()) {
1334 process.closeWriteChannel();
1335 }
1336 if (process.waitForFinished(pipeTimeout())) {
1337 success = (process.exitStatus() == QProcess::NormalExit);
1338 finished = true;
1339 } else {
1340 finished = false;
1341 success = false;
1342 }
1343 } else {
1344 success = false;
1345 finished = false;
1346 }
1347
1348 // The process has started, but did not finish in time. Kill it.
1349 if (!finished) {
1350 process.kill();
1351 }
1352 } else {
1353 success = false;
1354 }
1355
1356 if (!success && d->mDebug) {
1357 KMessageBox::error(nullptr, xi18nc("@info", "Pipe command <command>%1</command> failed.", cmd));
1358 }
1359
1360 if (success) {
1362 return codecFromName.decode(process.readAllStandardOutput());
1363 } else {
1364 return {};
1365 }
1366}
1367
1368void TemplateParserJob::setWordWrap(bool wrap, int wrapColWidth)
1369{
1370 d->mWrap = wrap;
1371 d->mColWrap = wrapColWidth;
1372}
1373
1374QString TemplateParserJob::plainMessageText(bool aStripSignature, AllowSelection isSelectionAllowed) const
1375{
1376 if (!d->mSelection.isEmpty() && (isSelectionAllowed == SelectionAllowed)) {
1377 return d->mSelection;
1378 }
1379
1380 if (!d->mOrigMsg) {
1381 return {};
1382 }
1383 const auto mp = toplevelTextNode(d->mOtp->parsedPart());
1384 QString result = mp->plaintextContent();
1385 if (result.isEmpty()) {
1386 result = d->mExtractHtmlInfoResult.mPlainText;
1387 }
1388 if (aStripSignature) {
1390 }
1391
1392 return result;
1393}
1394
1395QString TemplateParserJob::htmlMessageText(bool aStripSignature, AllowSelection isSelectionAllowed)
1396{
1397 if (!d->mSelection.isEmpty() && (isSelectionAllowed == SelectionAllowed)) {
1398 // TODO implement d->mSelection for HTML
1399 return d->mSelection;
1400 }
1401 d->mHeadElement = d->mExtractHtmlInfoResult.mHeaderElement;
1402 const QString bodyElement = d->mExtractHtmlInfoResult.mBodyElement;
1403 if (!bodyElement.isEmpty()) {
1404 if (aStripSignature) {
1405 // FIXME strip signature works partially for HTML mails
1406 return MessageCore::StringUtil::stripSignature(bodyElement);
1407 }
1408 return bodyElement;
1409 }
1410
1411 if (aStripSignature) {
1412 // FIXME strip signature works partially for HTML mails
1413 return MessageCore::StringUtil::stripSignature(d->mExtractHtmlInfoResult.mHtmlElement);
1414 }
1415 return d->mExtractHtmlInfoResult.mHtmlElement;
1416}
1417
1418QString TemplateParserJob::quotedPlainText(const QString &selection) const
1419{
1420 QString content = TemplateParser::Util::removeSpaceAtBegin(selection);
1421
1422 const QString indentStr = MessageCore::StringUtil::formatQuotePrefix(d->mQuoteString, d->mOrigMsg->from()->displayString());
1423 if (TemplateParserSettings::self()->smartQuote() && d->mWrap) {
1424 content = MessageCore::StringUtil::smartQuote(content, d->mColWrap - indentStr.length());
1425 }
1426 content.replace(QLatin1Char('\n'), QLatin1Char('\n') + indentStr);
1427 content.prepend(indentStr);
1428 content += QLatin1Char('\n');
1429
1430 return content;
1431}
1432
1433QString TemplateParserJob::quotedHtmlText(const QString &selection) const
1434{
1435 QString content = selection;
1436 // TODO 1) look for all the variations of <br> and remove the blank lines
1437 // 2) implement vertical bar for quoted HTML mail.
1438 // 3) After vertical bar is implemented, If a user wants to edit quoted message,
1439 // then the <blockquote> tags below should open and close as when required.
1440
1441 // Add blockquote tag, so that quoted message can be differentiated from normal message
1442 // Bug 419978 remove \n by <br>
1443 content = QLatin1StringView("<blockquote>") + content.replace(QStringLiteral("\n"), QStringLiteral("<br>")) + QLatin1StringView("</blockquote>");
1444 return content;
1445}
1446
1447uint TemplateParserJob::identityUoid(const KMime::Message::Ptr &msg) const
1448{
1449 QString idString;
1450 if (auto hrd = msg->headerByType("X-KMail-Identity")) {
1451 idString = hrd->asUnicodeString().trimmed();
1452 }
1453 bool ok = false;
1454 unsigned int id = idString.toUInt(&ok);
1455
1456 if (!ok || id == 0) {
1457 id = d->m_identityManager->identityForAddress(msg->to()->asUnicodeString() + QLatin1StringView(", ") + msg->cc()->asUnicodeString()).uoid();
1458 }
1459
1460 return id;
1461}
1462
1463bool TemplateParserJob::isHtmlSignature() const
1464{
1465 const KIdentityManagementCore::Identity &identity = d->m_identityManager->identityForUoid(d->mIdentity);
1466
1467 if (identity.isNull()) {
1468 return false;
1469 }
1470
1471 const KIdentityManagementCore::Signature signature = const_cast<KIdentityManagementCore::Identity &>(identity).signature();
1472
1473 return signature.isInlinedHtml();
1474}
1475
1476QString TemplateParserJob::plainTextToHtml(const QString &body)
1477{
1478 QString str = body;
1479 str = str.toHtmlEscaped();
1480 str.replace(QLatin1Char('\n'), QStringLiteral("<br />\n"));
1481 return str;
1482}
1483
1484void TemplateParserJob::makeValidHtml(QString &body)
1485{
1486 if (body.isEmpty()) {
1487 return;
1488 }
1489
1490 QRegularExpression regEx;
1491
1492 regEx.setPattern(QStringLiteral("<html.*?>"));
1493 if (!body.contains(regEx)) {
1494 regEx.setPattern(QStringLiteral("<body.*?>"));
1495 if (!body.contains(regEx)) {
1496 body = QLatin1StringView("<body>") + body + QLatin1StringView("<br/></body>");
1497 }
1498 regEx.setPattern(QStringLiteral("<head.*?>"));
1499 if (!body.contains(regEx)) {
1500 body = QLatin1StringView("<head>") + d->mHeadElement + QLatin1StringView("</head>") + body;
1501 }
1502 body = QLatin1StringView("<html>") + body + QLatin1StringView("</html>");
1503 }
1504}
1505
1506#include "moc_templateparserjob.cpp"
QString rawText(bool *ok=nullptr, QString *errorMessage=nullptr) const
bool hasContent() const
const Headers::ContentType * contentType() const
Content * content(const ContentIndex &index) const
const Headers::ContentTransferEncoding * contentTransferEncoding() const
void fromUnicodeString(const QString &s)
void setEncoding(contentEncoding e)
void setMimeType(const QByteArray &mimeType)
void start()
void setShellCommand(const QString &cmd)
void setOutputChannelMode(OutputChannelMode mode)
A helper class to collect the embedded images of a email.
void collectImagesFrom(KMime::Content *content)
Starts collecting the images.
const std::vector< KMime::Content * > & images() const
Returns the collected images.
Parses messages and generates HTML display code out of them.
void setIdentityManager(KIdentityManagementCore::IdentityManager *ident)
Set the identity manager to be used when creating the template.
void setWordWrap(bool wrap, int wrapColWidth=80)
Tell template parser whether or not to wrap words, and at what column to wrap at.
void setSelection(const QString &selection)
Sets the selection.
void setAllowDecryption(const bool allowDecryption)
Sets whether the template parser is allowed to decrypt the original message when needing its message ...
static QString configIdString(uint id)
Returns the template configuration identifier string for a given identity.
QString xi18nc(const char *context, const char *text, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QString path(const QString &relativePath)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
KCOREADDONS_EXPORT QString tildeExpand(const QString &path)
QString formatQuotePrefix(const QString &wildString, const QString &fromDisplayString)
Convert quote wildcards into the final quote prefix.
QByteArray headerAsSendableString(const KMime::Message::Ptr &originalMessage)
Return the message header with the headers that should not be sent stripped off.
QString stripSignature(const QString &msg)
Strips the signature blocks from a message text.
QString smartQuote(const QString &msg, int maxLineLength)
Relayouts the given string so that the individual lines don't exceed the given maximal length.
const char * constData() const const
qsizetype size() const const
int dayOfWeek() const const
QDateTime currentDateTime()
QDate date() const const
QTime time() const const
QString homePath()
qint64 write(const QByteArray &data)
bool contains(const AT &value) const const
QLocale c()
QString toString(QDate date, FormatType format) const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
void closeWriteChannel()
QProcess::ExitStatus exitStatus() const const
void kill()
QByteArray readAllStandardOutput()
virtual bool waitForBytesWritten(int msecs) override
bool waitForFinished(int msecs)
bool waitForStarted(int msecs)
void setPattern(const QString &pattern)
QString & append(QChar ch)
QString arg(Args &&... args) const const
void chop(qsizetype n)
void clear()
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
QString fromLocal8Bit(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
QString & prepend(QChar ch)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString toHtmlEscaped() const const
QByteArray toLatin1() const const
QByteArray toLocal8Bit() const const
uint toUInt(bool *ok, int base) const const
QString trimmed() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:59:13 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.