Messagelib

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

KDE's Doxygen guidelines are available online.