File indexing completed on 2025-02-16 04:57:42

0001 /*
0002    SPDX-FileCopyrightText: 2017-2024 Laurent Montel <montel@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "templateparserjob.h"
0008 #include "customtemplates_kfg.h"
0009 #include "globalsettings_templateparser.h"
0010 #include "templateparserextracthtmlinfo.h"
0011 #include "templateparserjob_p.h"
0012 #include "templatesconfiguration.h"
0013 #include "templatesconfiguration_kfg.h"
0014 #include "templatesutil.h"
0015 #include "templatesutil_p.h"
0016 
0017 #include <MessageCore/ImageCollector>
0018 #include <MessageCore/StringUtil>
0019 
0020 #include <MimeTreeParser/MessagePart>
0021 #include <MimeTreeParser/ObjectTreeParser>
0022 #include <MimeTreeParser/SimpleObjectTreeSource>
0023 
0024 #include <KIdentityManagementCore/Identity>
0025 #include <KIdentityManagementCore/IdentityManager>
0026 
0027 #include "templateparser_debug.h"
0028 #include <KLocalizedString>
0029 #include <KMessageBox>
0030 #include <KProcess>
0031 #include <KShell>
0032 
0033 #include <QDir>
0034 #include <QFile>
0035 #include <QFileInfo>
0036 #include <QLocale>
0037 #include <QRegularExpression>
0038 #include <QStringDecoder>
0039 
0040 namespace
0041 {
0042 Q_DECL_CONSTEXPR inline int pipeTimeout()
0043 {
0044     return 15 * 1000;
0045 }
0046 }
0047 
0048 using namespace TemplateParser;
0049 
0050 TemplateParserJobPrivate::TemplateParserJobPrivate(const KMime::Message::Ptr &amsg, const TemplateParserJob::Mode amode)
0051     : mMsg(amsg)
0052     , mMode(amode)
0053 {
0054     mEmptySource = new MimeTreeParser::SimpleObjectTreeSource;
0055     mEmptySource->setDecryptMessage(mAllowDecryption);
0056 
0057     mOtp = new MimeTreeParser::ObjectTreeParser(mEmptySource);
0058     mOtp->setAllowAsync(false);
0059 }
0060 
0061 TemplateParserJobPrivate::~TemplateParserJobPrivate()
0062 {
0063     delete mEmptySource;
0064     delete mOtp;
0065 }
0066 
0067 void TemplateParserJobPrivate::setAllowDecryption(const bool allowDecryption)
0068 {
0069     mAllowDecryption = allowDecryption;
0070     mEmptySource->setDecryptMessage(mAllowDecryption);
0071 }
0072 
0073 TemplateParserJob::TemplateParserJob(const KMime::Message::Ptr &amsg, const Mode amode, QObject *parent)
0074     : QObject(parent)
0075     , d(new TemplateParserJobPrivate(amsg, amode))
0076 {
0077 }
0078 
0079 TemplateParserJob::~TemplateParserJob() = default;
0080 
0081 void TemplateParserJob::setSelection(const QString &selection)
0082 {
0083     d->mSelection = selection;
0084 }
0085 
0086 void TemplateParserJob::setAllowDecryption(const bool allowDecryption)
0087 {
0088     d->setAllowDecryption(allowDecryption);
0089 }
0090 
0091 bool TemplateParserJob::shouldStripSignature() const
0092 {
0093     // Only strip the signature when replying, it should be preserved when forwarding
0094     return (d->mMode == Reply || d->mMode == ReplyAll) && TemplateParserSettings::self()->stripSignature();
0095 }
0096 
0097 void TemplateParserJob::setIdentityManager(KIdentityManagementCore::IdentityManager *ident)
0098 {
0099     d->m_identityManager = ident;
0100 }
0101 
0102 int TemplateParserJob::parseQuotes(const QString &prefix, const QString &str, QString &quote)
0103 {
0104     int pos = prefix.length();
0105     int len;
0106     const int str_len = str.length();
0107 
0108     // Also allow the german lower double-quote sign as quote separator, not only
0109     // the standard ASCII quote ("). This fixes bug 166728.
0110     const QList<QChar> quoteChars = {QLatin1Char('"'), QChar(0x201C)};
0111 
0112     QChar prev(QChar::Null);
0113 
0114     pos++;
0115     len = pos;
0116 
0117     while (pos < str_len) {
0118         const QChar c = str[pos];
0119 
0120         pos++;
0121         len++;
0122 
0123         if (!prev.isNull()) {
0124             quote.append(c);
0125             prev = QChar::Null;
0126         } else {
0127             if (c == QLatin1Char('\\')) {
0128                 prev = c;
0129             } else if (quoteChars.contains(c)) {
0130                 break;
0131             } else {
0132                 quote.append(c);
0133             }
0134         }
0135     }
0136 
0137     return len;
0138 }
0139 
0140 void TemplateParserJob::process(const KMime::Message::Ptr &aorig_msg, qint64 afolder)
0141 {
0142     if (aorig_msg == nullptr) {
0143         qCDebug(TEMPLATEPARSER_LOG) << "aorig_msg == 0!";
0144         Q_EMIT parsingDone(d->mForceCursorPosition);
0145         deleteLater();
0146         return;
0147     }
0148 
0149     d->mOrigMsg = aorig_msg;
0150     d->mFolder = afolder;
0151     const QString tmpl = findTemplate();
0152     if (tmpl.isEmpty()) {
0153         Q_EMIT parsingDone(d->mForceCursorPosition);
0154         deleteLater();
0155         return;
0156     }
0157     processWithTemplate(tmpl);
0158 }
0159 
0160 void TemplateParserJob::process(const QString &tmplName, const KMime::Message::Ptr &aorig_msg, qint64 afolder)
0161 {
0162     d->mForceCursorPosition = false;
0163     d->mOrigMsg = aorig_msg;
0164     d->mFolder = afolder;
0165     const QString tmpl = findCustomTemplate(tmplName);
0166     processWithTemplate(tmpl);
0167 }
0168 
0169 void TemplateParserJob::processWithIdentity(uint uoid, const KMime::Message::Ptr &aorig_msg, qint64 afolder)
0170 {
0171     d->mIdentity = uoid;
0172     process(aorig_msg, afolder);
0173 }
0174 
0175 MimeTreeParser::MessagePart::Ptr toplevelTextNode(MimeTreeParser::MessagePart::Ptr messageTree)
0176 {
0177     const auto subParts = messageTree->subParts();
0178     for (const auto &mp : subParts) {
0179         auto text = mp.dynamicCast<MimeTreeParser::TextMessagePart>();
0180         const auto attach = mp.dynamicCast<MimeTreeParser::AttachmentMessagePart>();
0181         if (text && !attach) {
0182             // TextMessagePart can have several subparts cause of PGP inline, we search for the first part with content
0183             const auto mpSubParts{mp->subParts()};
0184             for (const auto &sub : mpSubParts) {
0185                 if (!sub->text().trimmed().isEmpty()) {
0186                     return sub;
0187                 }
0188             }
0189             return text;
0190         } else if (const auto html = mp.dynamicCast<MimeTreeParser::HtmlMessagePart>()) {
0191             return html;
0192         } else if (const auto alternative = mp.dynamicCast<MimeTreeParser::AlternativeMessagePart>()) {
0193             return alternative;
0194         } else {
0195             auto ret = toplevelTextNode(mp);
0196             if (ret) {
0197                 return ret;
0198             }
0199         }
0200     }
0201     return {};
0202 }
0203 
0204 void TemplateParserJob::processWithTemplate(const QString &tmpl)
0205 {
0206     d->mOtp->parseObjectTree(d->mOrigMsg.data());
0207 
0208     const auto mp = toplevelTextNode(d->mOtp->parsedPart());
0209     if (!mp) {
0210         qCWarning(TEMPLATEPARSER_LOG) << "Invalid message! mp is null ";
0211         Q_EMIT parsingFailed();
0212         return;
0213     }
0214 
0215     QString plainText = mp->plaintextContent();
0216     QString htmlElement;
0217 
0218     if (mp->isHtml()) {
0219         htmlElement = d->mOtp->htmlContent();
0220         if (plainText.isEmpty()) { // HTML-only mails
0221             plainText = htmlElement;
0222         }
0223     } else { // plain mails only
0224         QString htmlReplace = plainText.toHtmlEscaped();
0225         htmlReplace.replace(QLatin1Char('\n'), QStringLiteral("<br />"));
0226         htmlElement = QStringLiteral("<html><head></head><body>%1</body></html>\n").arg(htmlReplace);
0227     }
0228 
0229     auto job = new TemplateParserExtractHtmlInfo(this);
0230     connect(job, &TemplateParserExtractHtmlInfo::finished, this, &TemplateParserJob::slotExtractInfoDone);
0231 
0232     job->setHtmlForExtractingTextPlain(plainText);
0233     job->setTemplate(tmpl);
0234 
0235     job->setHtmlForExtractionHeaderAndBody(htmlElement);
0236     job->start();
0237 }
0238 
0239 void TemplateParserJob::setReplyAsHtml(bool replyAsHtml)
0240 {
0241     d->mReplyAsHtml = replyAsHtml;
0242 }
0243 
0244 void TemplateParserJob::slotExtractInfoDone(const TemplateParserExtractHtmlInfoResult &result)
0245 {
0246     d->mExtractHtmlInfoResult = result;
0247     const QString tmpl = d->mExtractHtmlInfoResult.mTemplate;
0248     const int tmpl_len = tmpl.length();
0249     QString plainBody;
0250     QString htmlBody;
0251 
0252     bool dnl = false;
0253     auto definedLocale = QLocale();
0254     for (int i = 0; i < tmpl_len; ++i) {
0255         QChar c = tmpl[i];
0256         // qCDebug(TEMPLATEPARSER_LOG) << "Next char: " << c;
0257         if (c == QLatin1Char('%')) {
0258             const QString cmd = tmpl.mid(i + 1);
0259 
0260             if (cmd.startsWith(QLatin1Char('-'))) {
0261                 // dnl
0262                 qCDebug(TEMPLATEPARSER_LOG) << "Command: -";
0263                 dnl = true;
0264                 i += 1;
0265             } else if (cmd.startsWith(QLatin1StringView("REM="))) {
0266                 // comments
0267                 qCDebug(TEMPLATEPARSER_LOG) << "Command: REM=";
0268                 QString q;
0269                 const int len = parseQuotes(QStringLiteral("REM="), cmd, q);
0270                 i += len;
0271             } else if (cmd.startsWith(QLatin1StringView("LANGUAGE="))) {
0272                 QString q;
0273                 const int len = parseQuotes(QStringLiteral("LANGUAGE="), cmd, q);
0274                 i += len;
0275                 if (!q.isEmpty()) {
0276                     definedLocale = QLocale(q);
0277                 }
0278             } else if (cmd.startsWith(QLatin1StringView("DICTIONARYLANGUAGE="))) {
0279                 QString q;
0280                 const int len = parseQuotes(QStringLiteral("DICTIONARYLANGUAGE="), cmd, q);
0281                 i += len;
0282                 if (!q.isEmpty()) {
0283                     auto header = new KMime::Headers::Generic("X-KMail-Dictionary");
0284                     header->fromUnicodeString(q, "utf-8");
0285                     d->mMsg->setHeader(header);
0286                 }
0287             } else if (cmd.startsWith(QLatin1StringView("INSERT=")) || cmd.startsWith(QLatin1StringView("PUT="))) {
0288                 QString q;
0289                 int len = 0;
0290                 if (cmd.startsWith(QLatin1StringView("INSERT="))) {
0291                     // insert content of specified file as is
0292                     qCDebug(TEMPLATEPARSER_LOG) << "Command: INSERT=";
0293                     len = parseQuotes(QStringLiteral("INSERT="), cmd, q);
0294                 } else {
0295                     // insert content of specified file as is
0296                     qCDebug(TEMPLATEPARSER_LOG) << "Command: PUT=";
0297                     len = parseQuotes(QStringLiteral("PUT="), cmd, q);
0298                 }
0299                 i += len;
0300                 QString path = KShell::tildeExpand(q);
0301                 QFileInfo finfo(path);
0302                 if (finfo.isRelative()) {
0303                     path = QDir::homePath() + QLatin1Char('/') + q;
0304                 }
0305                 QFile file(path);
0306                 if (file.open(QIODevice::ReadOnly)) {
0307                     const QByteArray content = file.readAll();
0308                     const QString str = QString::fromLocal8Bit(content.constData(), content.size());
0309                     plainBody.append(str);
0310                     const QString body = plainTextToHtml(str);
0311                     htmlBody.append(body);
0312                 } else if (d->mDebug) {
0313                     KMessageBox::error(nullptr, i18nc("@info", "Cannot insert content from file %1: %2", path, file.errorString()));
0314                 }
0315             } else if (cmd.startsWith(QLatin1StringView("SYSTEM="))) {
0316                 // insert content of specified file as is
0317                 qCDebug(TEMPLATEPARSER_LOG) << "Command: SYSTEM=";
0318                 QString q;
0319                 const int len = parseQuotes(QStringLiteral("SYSTEM="), cmd, q);
0320                 i += len;
0321                 const QString pipe_cmd = q;
0322                 const QString str = pipe(pipe_cmd, QString());
0323                 plainBody.append(str);
0324                 const QString body = plainTextToHtml(str);
0325                 htmlBody.append(body);
0326             } else if (cmd.startsWith(QLatin1StringView("QUOTEPIPE="))) {
0327                 // pipe message body through command and insert it as quotation
0328                 qCDebug(TEMPLATEPARSER_LOG) << "Command: QUOTEPIPE=";
0329                 QString q;
0330                 const int len = parseQuotes(QStringLiteral("QUOTEPIPE="), cmd, q);
0331                 i += len;
0332                 const QString pipe_cmd = q;
0333                 if (d->mOrigMsg) {
0334                     const QString plainStr = pipe(pipe_cmd, plainMessageText(shouldStripSignature(), NoSelectionAllowed));
0335                     QString plainQuote = quotedPlainText(plainStr);
0336                     if (plainQuote.endsWith(QLatin1Char('\n'))) {
0337                         plainQuote.chop(1);
0338                     }
0339                     plainBody.append(plainQuote);
0340 
0341                     const QString htmlStr = pipe(pipe_cmd, htmlMessageText(shouldStripSignature(), NoSelectionAllowed));
0342                     const QString htmlQuote = quotedHtmlText(htmlStr);
0343                     htmlBody.append(htmlQuote);
0344                 }
0345             } else if (cmd.startsWith(QLatin1StringView("QUOTE"))) {
0346                 qCDebug(TEMPLATEPARSER_LOG) << "Command: QUOTE";
0347                 i += strlen("QUOTE");
0348                 if (d->mOrigMsg) {
0349                     QString plainQuote = quotedPlainText(plainMessageText(shouldStripSignature(), SelectionAllowed));
0350                     if (plainQuote.endsWith(QLatin1Char('\n'))) {
0351                         plainQuote.chop(1);
0352                     }
0353                     plainBody.append(plainQuote);
0354 
0355                     const QString htmlQuote = quotedHtmlText(htmlMessageText(shouldStripSignature(), SelectionAllowed));
0356                     htmlBody.append(htmlQuote);
0357                 }
0358             } else if (cmd.startsWith(QLatin1StringView("FORCEDPLAIN"))) {
0359                 qCDebug(TEMPLATEPARSER_LOG) << "Command: FORCEDPLAIN";
0360                 d->mQuotes = ReplyAsPlain;
0361                 i += strlen("FORCEDPLAIN");
0362             } else if (cmd.startsWith(QLatin1StringView("FORCEDHTML"))) {
0363                 qCDebug(TEMPLATEPARSER_LOG) << "Command: FORCEDHTML";
0364                 d->mQuotes = ReplyAsHtml;
0365                 i += strlen("FORCEDHTML");
0366             } else if (cmd.startsWith(QLatin1StringView("QHEADERS"))) {
0367                 qCDebug(TEMPLATEPARSER_LOG) << "Command: QHEADERS";
0368                 i += strlen("QHEADERS");
0369                 if (d->mOrigMsg) {
0370                     const QString headerStr = QString::fromLatin1(MessageCore::StringUtil::headerAsSendableString(d->mOrigMsg));
0371                     QString plainQuote = quotedPlainText(headerStr);
0372                     if (plainQuote.endsWith(QLatin1Char('\n'))) {
0373                         plainQuote.chop(1);
0374                     }
0375                     plainBody.append(plainQuote);
0376 
0377                     const QString htmlQuote = quotedHtmlText(headerStr);
0378                     const QString str = plainTextToHtml(htmlQuote);
0379                     htmlBody.append(str);
0380                 }
0381             } else if (cmd.startsWith(QLatin1StringView("HEADERS"))) {
0382                 qCDebug(TEMPLATEPARSER_LOG) << "Command: HEADERS";
0383                 i += strlen("HEADERS");
0384                 if (d->mOrigMsg) {
0385                     const QString str = QString::fromLatin1(MessageCore::StringUtil::headerAsSendableString(d->mOrigMsg));
0386                     plainBody.append(str);
0387                     const QString body = plainTextToHtml(str);
0388                     htmlBody.append(body);
0389                 }
0390             } else if (cmd.startsWith(QLatin1StringView("TEXTPIPE="))) {
0391                 // pipe message body through command and insert it as is
0392                 qCDebug(TEMPLATEPARSER_LOG) << "Command: TEXTPIPE=";
0393                 QString q;
0394                 const int len = parseQuotes(QStringLiteral("TEXTPIPE="), cmd, q);
0395                 i += len;
0396                 const QString pipe_cmd = q;
0397                 if (d->mOrigMsg) {
0398                     const QString plainStr = pipe(pipe_cmd, plainMessageText(shouldStripSignature(), NoSelectionAllowed));
0399                     plainBody.append(plainStr);
0400 
0401                     const QString htmlStr = pipe(pipe_cmd, htmlMessageText(shouldStripSignature(), NoSelectionAllowed));
0402                     htmlBody.append(htmlStr);
0403                 }
0404             } else if (cmd.startsWith(QLatin1StringView("MSGPIPE="))) {
0405                 // pipe full message through command and insert result as is
0406                 qCDebug(TEMPLATEPARSER_LOG) << "Command: MSGPIPE=";
0407                 QString q;
0408                 const int len = parseQuotes(QStringLiteral("MSGPIPE="), cmd, q);
0409                 i += len;
0410                 if (d->mOrigMsg) {
0411                     const QString str = pipe(q, QString::fromLatin1(d->mOrigMsg->encodedContent()));
0412                     plainBody.append(str);
0413 
0414                     const QString body = plainTextToHtml(str);
0415                     htmlBody.append(body);
0416                 }
0417             } else if (cmd.startsWith(QLatin1StringView("BODYPIPE="))) {
0418                 // pipe message body generated so far through command and insert result as is
0419                 qCDebug(TEMPLATEPARSER_LOG) << "Command: BODYPIPE=";
0420                 QString q;
0421                 const int len = parseQuotes(QStringLiteral("BODYPIPE="), cmd, q);
0422                 i += len;
0423                 const QString pipe_cmd = q;
0424                 const QString plainStr = pipe(q, plainBody);
0425                 plainBody.append(plainStr);
0426 
0427                 const QString htmlStr = pipe(pipe_cmd, htmlBody);
0428                 const QString body = plainTextToHtml(htmlStr);
0429                 htmlBody.append(body);
0430             } else if (cmd.startsWith(QLatin1StringView("CLEARPIPE="))) {
0431                 // pipe message body generated so far through command and
0432                 // insert result as is replacing current body
0433                 qCDebug(TEMPLATEPARSER_LOG) << "Command: CLEARPIPE=";
0434                 QString q;
0435                 const int len = parseQuotes(QStringLiteral("CLEARPIPE="), cmd, q);
0436                 i += len;
0437                 const QString pipe_cmd = q;
0438                 const QString plainStr = pipe(pipe_cmd, plainBody);
0439                 plainBody = plainStr;
0440 
0441                 const QString htmlStr = pipe(pipe_cmd, htmlBody);
0442                 htmlBody = htmlStr;
0443 
0444                 auto header = new KMime::Headers::Generic("X-KMail-CursorPos");
0445                 header->fromUnicodeString(QString::number(0), "utf-8");
0446                 d->mMsg->setHeader(header);
0447             } else if (cmd.startsWith(QLatin1StringView("TEXT"))) {
0448                 qCDebug(TEMPLATEPARSER_LOG) << "Command: TEXT";
0449                 i += strlen("TEXT");
0450                 if (d->mOrigMsg) {
0451                     const QString plainStr = plainMessageText(shouldStripSignature(), NoSelectionAllowed);
0452                     plainBody.append(plainStr);
0453 
0454                     const QString htmlStr = htmlMessageText(shouldStripSignature(), NoSelectionAllowed);
0455                     htmlBody.append(htmlStr);
0456                 }
0457             } else if (cmd.startsWith(QLatin1StringView("OTEXTSIZE"))) {
0458                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTEXTSIZE";
0459                 i += strlen("OTEXTSIZE");
0460                 if (d->mOrigMsg) {
0461                     const QString str = QStringLiteral("%1").arg(d->mOrigMsg->body().length());
0462                     plainBody.append(str);
0463                     const QString body = plainTextToHtml(str);
0464                     htmlBody.append(body);
0465                 }
0466             } else if (cmd.startsWith(QLatin1StringView("OTEXT"))) {
0467                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTEXT";
0468                 i += strlen("OTEXT");
0469                 if (d->mOrigMsg) {
0470                     const QString plainStr = plainMessageText(shouldStripSignature(), NoSelectionAllowed);
0471                     plainBody.append(plainStr);
0472 
0473                     const QString htmlStr = htmlMessageText(shouldStripSignature(), NoSelectionAllowed);
0474                     htmlBody.append(htmlStr);
0475                 }
0476             } else if (cmd.startsWith(QLatin1StringView("OADDRESSEESADDR"))) {
0477                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OADDRESSEESADDR";
0478                 i += strlen("OADDRESSEESADDR");
0479                 if (d->mOrigMsg) {
0480                     const QString to = d->mOrigMsg->to()->asUnicodeString();
0481                     const QString cc = d->mOrigMsg->cc()->asUnicodeString();
0482                     if (!to.isEmpty()) {
0483                         const QString toLine = i18nc("@item:intext email To", "To:") + QLatin1Char(' ') + to;
0484                         plainBody.append(toLine);
0485                         const QString body = plainTextToHtml(toLine);
0486                         htmlBody.append(body);
0487                     }
0488                     if (!to.isEmpty() && !cc.isEmpty()) {
0489                         plainBody.append(QLatin1Char('\n'));
0490                         const QString str = plainTextToHtml(QString(QLatin1Char('\n')));
0491                         htmlBody.append(str);
0492                     }
0493                     if (!cc.isEmpty()) {
0494                         const QString ccLine = i18nc("@item:intext email CC", "CC:") + QLatin1Char(' ') + cc;
0495                         plainBody.append(ccLine);
0496                         const QString str = plainTextToHtml(ccLine);
0497                         htmlBody.append(str);
0498                     }
0499                 }
0500             } else if (cmd.startsWith(QLatin1StringView("CCADDR"))) {
0501                 qCDebug(TEMPLATEPARSER_LOG) << "Command: CCADDR";
0502                 i += strlen("CCADDR");
0503                 const QString str = d->mMsg->cc()->asUnicodeString();
0504                 plainBody.append(str);
0505                 const QString body = plainTextToHtml(str);
0506                 htmlBody.append(body);
0507             } else if (cmd.startsWith(QLatin1StringView("CCNAME"))) {
0508                 qCDebug(TEMPLATEPARSER_LOG) << "Command: CCNAME";
0509                 i += strlen("CCNAME");
0510                 const QString str = d->mMsg->cc()->displayString();
0511                 plainBody.append(str);
0512                 const QString body = plainTextToHtml(str);
0513                 htmlBody.append(body);
0514             } else if (cmd.startsWith(QLatin1StringView("CCFNAME"))) {
0515                 qCDebug(TEMPLATEPARSER_LOG) << "Command: CCFNAME";
0516                 i += strlen("CCFNAME");
0517                 const QString str = d->mMsg->cc()->displayString();
0518                 const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str);
0519                 plainBody.append(firstNameFromEmail);
0520                 const QString body = plainTextToHtml(firstNameFromEmail);
0521                 htmlBody.append(body);
0522             } else if (cmd.startsWith(QLatin1StringView("CCLNAME"))) {
0523                 qCDebug(TEMPLATEPARSER_LOG) << "Command: CCLNAME";
0524                 i += strlen("CCLNAME");
0525                 const QString str = d->mMsg->cc()->displayString();
0526                 plainBody.append(TemplateParser::Util::getLastNameFromEmail(str));
0527                 const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str));
0528                 htmlBody.append(body);
0529             } else if (cmd.startsWith(QLatin1StringView("TOADDR"))) {
0530                 qCDebug(TEMPLATEPARSER_LOG) << "Command: TOADDR";
0531                 i += strlen("TOADDR");
0532                 const QString str = d->mMsg->to()->asUnicodeString();
0533                 plainBody.append(str);
0534                 const QString body = plainTextToHtml(str);
0535                 htmlBody.append(body);
0536             } else if (cmd.startsWith(QLatin1StringView("TONAME"))) {
0537                 qCDebug(TEMPLATEPARSER_LOG) << "Command: TONAME";
0538                 i += strlen("TONAME");
0539                 const QString str = (d->mMsg->to()->displayString());
0540                 plainBody.append(str);
0541                 const QString body = plainTextToHtml(str);
0542                 htmlBody.append(body);
0543             } else if (cmd.startsWith(QLatin1StringView("TOFNAME"))) {
0544                 qCDebug(TEMPLATEPARSER_LOG) << "Command: TOFNAME";
0545                 i += strlen("TOFNAME");
0546                 const QString str = d->mMsg->to()->displayString();
0547                 const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str);
0548                 plainBody.append(firstNameFromEmail);
0549                 const QString body = plainTextToHtml(firstNameFromEmail);
0550                 htmlBody.append(body);
0551             } else if (cmd.startsWith(QLatin1StringView("TOLNAME"))) {
0552                 qCDebug(TEMPLATEPARSER_LOG) << "Command: TOLNAME";
0553                 i += strlen("TOLNAME");
0554                 const QString str = d->mMsg->to()->displayString();
0555                 plainBody.append(TemplateParser::Util::getLastNameFromEmail(str));
0556                 const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str));
0557                 htmlBody.append(body);
0558             } else if (cmd.startsWith(QLatin1StringView("TOLIST"))) {
0559                 qCDebug(TEMPLATEPARSER_LOG) << "Command: TOLIST";
0560                 i += strlen("TOLIST");
0561                 const QString str = d->mMsg->to()->asUnicodeString();
0562                 plainBody.append(str);
0563                 const QString body = plainTextToHtml(str);
0564                 htmlBody.append(body);
0565             } else if (cmd.startsWith(QLatin1StringView("FROMADDR"))) {
0566                 qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMADDR";
0567                 i += strlen("FROMADDR");
0568                 const QString str = d->mMsg->from()->asUnicodeString();
0569                 plainBody.append(str);
0570                 const QString body = plainTextToHtml(str);
0571                 htmlBody.append(body);
0572             } else if (cmd.startsWith(QLatin1StringView("FROMNAME"))) {
0573                 qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMNAME";
0574                 i += strlen("FROMNAME");
0575                 const QString str = d->mMsg->from()->displayString();
0576                 plainBody.append(str);
0577                 const QString body = plainTextToHtml(str);
0578                 htmlBody.append(body);
0579             } else if (cmd.startsWith(QLatin1StringView("FROMFNAME"))) {
0580                 qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMFNAME";
0581                 i += strlen("FROMFNAME");
0582                 const QString str = d->mMsg->from()->displayString();
0583                 const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str);
0584                 plainBody.append(firstNameFromEmail);
0585                 const QString body = plainTextToHtml(firstNameFromEmail);
0586                 htmlBody.append(body);
0587             } else if (cmd.startsWith(QLatin1StringView("FROMLNAME"))) {
0588                 qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMLNAME";
0589                 i += strlen("FROMLNAME");
0590                 const QString str = d->mMsg->from()->displayString();
0591                 plainBody.append(TemplateParser::Util::getLastNameFromEmail(str));
0592                 const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str));
0593                 htmlBody.append(body);
0594             } else if (cmd.startsWith(QLatin1StringView("FULLSUBJECT")) || cmd.startsWith(QLatin1StringView("FULLSUBJ"))) {
0595                 if (cmd.startsWith(QLatin1StringView("FULLSUBJ"))) {
0596                     qCDebug(TEMPLATEPARSER_LOG) << "Command: FULLSUBJ";
0597                     i += strlen("FULLSUBJ");
0598                 } else {
0599                     qCDebug(TEMPLATEPARSER_LOG) << "Command: FULLSUBJECT";
0600                     i += strlen("FULLSUBJECT");
0601                 }
0602                 const QString str = d->mMsg->subject()->asUnicodeString();
0603                 plainBody.append(str);
0604                 const QString body = plainTextToHtml(str);
0605                 htmlBody.append(body);
0606             } else if (cmd.startsWith(QLatin1StringView("MSGID"))) {
0607                 qCDebug(TEMPLATEPARSER_LOG) << "Command: MSGID";
0608                 i += strlen("MSGID");
0609                 const QString str = d->mMsg->messageID()->asUnicodeString();
0610                 plainBody.append(str);
0611                 const QString body = plainTextToHtml(str);
0612                 htmlBody.append(body);
0613             } else if (cmd.startsWith(QLatin1StringView("OHEADER="))) {
0614                 // insert specified content of header from original message
0615                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OHEADER=";
0616                 QString q;
0617                 const int len = parseQuotes(QStringLiteral("OHEADER="), cmd, q);
0618                 i += len;
0619                 if (d->mOrigMsg) {
0620                     const QString hdr = q;
0621                     QString str;
0622                     if (auto hrdMsgOrigin = d->mOrigMsg->headerByType(hdr.toLocal8Bit().constData())) {
0623                         str = hrdMsgOrigin->asUnicodeString();
0624                     }
0625                     plainBody.append(str);
0626                     const QString body = plainTextToHtml(str);
0627                     htmlBody.append(body);
0628                 }
0629             } else if (cmd.startsWith(QLatin1StringView("HEADER="))) {
0630                 // insert specified content of header from current message
0631                 qCDebug(TEMPLATEPARSER_LOG) << "Command: HEADER=";
0632                 QString q;
0633                 const int len = parseQuotes(QStringLiteral("HEADER="), cmd, q);
0634                 i += len;
0635                 const QString hdr = q;
0636                 QString str;
0637                 if (auto hrdMsgOrigin = d->mOrigMsg->headerByType(hdr.toLocal8Bit().constData())) {
0638                     str = hrdMsgOrigin->asUnicodeString();
0639                 }
0640                 plainBody.append(str);
0641                 const QString body = plainTextToHtml(str);
0642                 htmlBody.append(body);
0643             } else if (cmd.startsWith(QLatin1StringView("HEADER( "))) {
0644                 // insert specified content of header from current message
0645                 qCDebug(TEMPLATEPARSER_LOG) << "Command: HEADER(";
0646                 QRegularExpressionMatch match;
0647                 static QRegularExpression reg(QStringLiteral("^HEADER\\((.+)\\)"));
0648                 const int res = cmd.indexOf(reg, 0, &match);
0649                 if (res != 0) {
0650                     // something wrong
0651                     i += strlen("HEADER( ");
0652                 } else {
0653                     i += match.capturedLength(0); // length of HEADER(<space> + <space>)
0654                     const QString hdr = match.captured(1).trimmed();
0655                     QString str;
0656                     if (auto hrdMsgOrigin = d->mOrigMsg->headerByType(hdr.toLocal8Bit().constData())) {
0657                         str = hrdMsgOrigin->asUnicodeString();
0658                     }
0659                     plainBody.append(str);
0660                     const QString body = plainTextToHtml(str);
0661                     htmlBody.append(body);
0662                 }
0663             } else if (cmd.startsWith(QLatin1StringView("OCCADDR"))) {
0664                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCADDR";
0665                 i += strlen("OCCADDR");
0666                 if (d->mOrigMsg) {
0667                     const QString str = d->mOrigMsg->cc()->asUnicodeString();
0668                     plainBody.append(str);
0669                     const QString body = plainTextToHtml(str);
0670                     htmlBody.append(body);
0671                 }
0672             } else if (cmd.startsWith(QLatin1StringView("OCCNAME"))) {
0673                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCNAME";
0674                 i += strlen("OCCNAME");
0675                 if (d->mOrigMsg) {
0676                     const QString str = d->mOrigMsg->cc()->displayString();
0677                     plainBody.append(str);
0678                     const QString body = plainTextToHtml(str);
0679                     htmlBody.append(body);
0680                 }
0681             } else if (cmd.startsWith(QLatin1StringView("OCCFNAME"))) {
0682                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCFNAME";
0683                 i += strlen("OCCFNAME");
0684                 if (d->mOrigMsg) {
0685                     const QString str = d->mOrigMsg->cc()->displayString();
0686                     const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str);
0687                     plainBody.append(firstNameFromEmail);
0688                     const QString body = plainTextToHtml(firstNameFromEmail);
0689                     htmlBody.append(body);
0690                 }
0691             } else if (cmd.startsWith(QLatin1StringView("OCCLNAME"))) {
0692                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCLNAME";
0693                 i += strlen("OCCLNAME");
0694                 if (d->mOrigMsg) {
0695                     const QString str = d->mOrigMsg->cc()->displayString();
0696                     plainBody.append(TemplateParser::Util::getLastNameFromEmail(str));
0697                     const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str));
0698                     htmlBody.append(body);
0699                 }
0700             } else if (cmd.startsWith(QLatin1StringView("OTOADDR"))) {
0701                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOADDR";
0702                 i += strlen("OTOADDR");
0703                 if (d->mOrigMsg) {
0704                     const QString str = d->mOrigMsg->to()->asUnicodeString();
0705                     plainBody.append(str);
0706                     const QString body = plainTextToHtml(str);
0707                     htmlBody.append(body);
0708                 }
0709             } else if (cmd.startsWith(QLatin1StringView("OTONAME"))) {
0710                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTONAME";
0711                 i += strlen("OTONAME");
0712                 if (d->mOrigMsg) {
0713                     const QString str = d->mOrigMsg->to()->displayString();
0714                     plainBody.append(str);
0715                     const QString body = plainTextToHtml(str);
0716                     htmlBody.append(body);
0717                 }
0718             } else if (cmd.startsWith(QLatin1StringView("OTOFNAME"))) {
0719                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOFNAME";
0720                 i += strlen("OTOFNAME");
0721                 if (d->mOrigMsg) {
0722                     const QString str = d->mOrigMsg->to()->displayString();
0723                     const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str);
0724                     plainBody.append(firstNameFromEmail);
0725                     const QString body = plainTextToHtml(firstNameFromEmail);
0726                     htmlBody.append(body);
0727                 }
0728             } else if (cmd.startsWith(QLatin1StringView("OTOLNAME"))) {
0729                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOLNAME";
0730                 i += strlen("OTOLNAME");
0731                 if (d->mOrigMsg) {
0732                     const QString str = d->mOrigMsg->to()->displayString();
0733                     plainBody.append(TemplateParser::Util::getLastNameFromEmail(str));
0734                     const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str));
0735                     htmlBody.append(body);
0736                 }
0737             } else if (cmd.startsWith(QLatin1StringView("OTOLIST"))) {
0738                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOLIST";
0739                 i += strlen("OTOLIST");
0740                 if (d->mOrigMsg) {
0741                     const QString str = d->mOrigMsg->to()->asUnicodeString();
0742                     plainBody.append(str);
0743                     const QString body = plainTextToHtml(str);
0744                     htmlBody.append(body);
0745                 }
0746             } else if (cmd.startsWith(QLatin1StringView("OTO"))) {
0747                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTO";
0748                 i += strlen("OTO");
0749                 if (d->mOrigMsg) {
0750                     const QString str = d->mOrigMsg->to()->asUnicodeString();
0751                     plainBody.append(str);
0752                     const QString body = plainTextToHtml(str);
0753                     htmlBody.append(body);
0754                 }
0755             } else if (cmd.startsWith(QLatin1StringView("OFROMADDR"))) {
0756                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMADDR";
0757                 i += strlen("OFROMADDR");
0758                 if (d->mOrigMsg) {
0759                     const QString str = d->mOrigMsg->from()->asUnicodeString();
0760                     plainBody.append(str);
0761                     const QString body = plainTextToHtml(str);
0762                     htmlBody.append(body);
0763                 }
0764             } else if (cmd.startsWith(QLatin1StringView("OFROMNAME"))) {
0765                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMNAME";
0766                 i += strlen("OFROMNAME");
0767                 if (d->mOrigMsg) {
0768                     const QString str = d->mOrigMsg->from()->displayString();
0769                     plainBody.append(str);
0770                     const QString body = plainTextToHtml(str);
0771                     htmlBody.append(body);
0772                 }
0773             } else if (cmd.startsWith(QLatin1StringView("OFROMFNAME"))) {
0774                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMFNAME";
0775                 i += strlen("OFROMFNAME");
0776                 if (d->mOrigMsg) {
0777                     const QString str = d->mOrigMsg->from()->displayString();
0778                     const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str);
0779                     plainBody.append(firstNameFromEmail);
0780                     const QString body = plainTextToHtml(firstNameFromEmail);
0781                     htmlBody.append(body);
0782                 }
0783             } else if (cmd.startsWith(QLatin1StringView("OFROMLNAME"))) {
0784                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMLNAME";
0785                 i += strlen("OFROMLNAME");
0786                 if (d->mOrigMsg) {
0787                     const QString str = d->mOrigMsg->from()->displayString();
0788                     plainBody.append(TemplateParser::Util::getLastNameFromEmail(str));
0789                     const QString body = plainTextToHtml(TemplateParser::Util::getLastNameFromEmail(str));
0790                     htmlBody.append(body);
0791                 }
0792             } else if (cmd.startsWith(QLatin1StringView("OFULLSUBJECT")) || cmd.startsWith(QLatin1StringView("OFULLSUBJ"))) {
0793                 if (cmd.startsWith(QLatin1StringView("OFULLSUBJECT"))) {
0794                     qCDebug(TEMPLATEPARSER_LOG) << "Command: OFULLSUBJECT";
0795                     i += strlen("OFULLSUBJECT");
0796                 } else {
0797                     qCDebug(TEMPLATEPARSER_LOG) << "Command: OFULLSUBJ";
0798                     i += strlen("OFULLSUBJ");
0799                 }
0800                 if (d->mOrigMsg) {
0801                     const QString str = d->mOrigMsg->subject()->asUnicodeString();
0802                     plainBody.append(str);
0803                     const QString body = plainTextToHtml(str);
0804                     htmlBody.append(body);
0805                 }
0806             } else if (cmd.startsWith(QLatin1StringView("OMSGID"))) {
0807                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OMSGID";
0808                 i += strlen("OMSGID");
0809                 if (d->mOrigMsg) {
0810                     const QString str = d->mOrigMsg->messageID()->asUnicodeString();
0811                     plainBody.append(str);
0812                     const QString body = plainTextToHtml(str);
0813                     htmlBody.append(body);
0814                 }
0815             } else if (cmd.startsWith(QLatin1StringView("DATEEN"))) {
0816                 qCDebug(TEMPLATEPARSER_LOG) << "Command: DATEEN";
0817                 i += strlen("DATEEN");
0818                 const QDateTime date = QDateTime::currentDateTime();
0819                 QLocale locale(QLocale::C);
0820                 const QString str = locale.toString(date.date(), QLocale::LongFormat);
0821                 plainBody.append(str);
0822                 const QString body = plainTextToHtml(str);
0823                 htmlBody.append(body);
0824             } else if (cmd.startsWith(QLatin1StringView("DATESHORT"))) {
0825                 qCDebug(TEMPLATEPARSER_LOG) << "Command: DATESHORT";
0826                 i += strlen("DATESHORT");
0827                 const QDateTime date = QDateTime::currentDateTime();
0828                 const QString str = definedLocale.toString(date.date(), QLocale::ShortFormat);
0829                 plainBody.append(str);
0830                 const QString body = plainTextToHtml(str);
0831                 htmlBody.append(body);
0832             } else if (cmd.startsWith(QLatin1StringView("DATE"))) {
0833                 qCDebug(TEMPLATEPARSER_LOG) << "Command: DATE";
0834                 i += strlen("DATE");
0835                 const QDateTime date = QDateTime::currentDateTime();
0836                 const QString str = definedLocale.toString(date.date(), QLocale::LongFormat);
0837                 plainBody.append(str);
0838                 const QString body = plainTextToHtml(str);
0839                 htmlBody.append(body);
0840             } else if (cmd.startsWith(QLatin1StringView("DOW"))) {
0841                 qCDebug(TEMPLATEPARSER_LOG) << "Command: DOW";
0842                 i += strlen("DOW");
0843                 const QDateTime date = QDateTime::currentDateTime();
0844                 const QString str = definedLocale.dayName(date.date().dayOfWeek(), QLocale::LongFormat);
0845                 plainBody.append(str);
0846                 const QString body = plainTextToHtml(str);
0847                 htmlBody.append(body);
0848             } else if (cmd.startsWith(QLatin1StringView("TIMELONGEN"))) {
0849                 qCDebug(TEMPLATEPARSER_LOG) << "Command: TIMELONGEN";
0850                 i += strlen("TIMELONGEN");
0851                 const QDateTime date = QDateTime::currentDateTime();
0852                 QLocale locale(QLocale::C);
0853                 const QString str = locale.toString(date.time(), QLocale::LongFormat);
0854                 plainBody.append(str);
0855                 const QString body = plainTextToHtml(str);
0856                 htmlBody.append(body);
0857             } else if (cmd.startsWith(QLatin1StringView("TIMELONG"))) {
0858                 qCDebug(TEMPLATEPARSER_LOG) << "Command: TIMELONG";
0859                 i += strlen("TIMELONG");
0860                 const QDateTime date = QDateTime::currentDateTime();
0861                 const QString str = definedLocale.toString(date.time(), QLocale::LongFormat);
0862                 plainBody.append(str);
0863                 const QString body = plainTextToHtml(str);
0864                 htmlBody.append(body);
0865             } else if (cmd.startsWith(QLatin1StringView("TIME"))) {
0866                 qCDebug(TEMPLATEPARSER_LOG) << "Command: TIME";
0867                 i += strlen("TIME");
0868                 const QDateTime date = QDateTime::currentDateTime();
0869                 const QString str = definedLocale.toString(date.time(), QLocale::ShortFormat);
0870                 plainBody.append(str);
0871                 const QString body = plainTextToHtml(str);
0872                 htmlBody.append(body);
0873             } else if (cmd.startsWith(QLatin1StringView("ODATEEN"))) {
0874                 qCDebug(TEMPLATEPARSER_LOG) << "Command: ODATEEN";
0875                 i += strlen("ODATEEN");
0876                 if (d->mOrigMsg) {
0877                     const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
0878                     const QString str = QLocale::c().toString(date.date(), QLocale::LongFormat);
0879                     plainBody.append(str);
0880                     const QString body = plainTextToHtml(str);
0881                     htmlBody.append(body);
0882                 }
0883             } else if (cmd.startsWith(QLatin1StringView("ODATESHORT"))) {
0884                 qCDebug(TEMPLATEPARSER_LOG) << "Command: ODATESHORT";
0885                 i += strlen("ODATESHORT");
0886                 if (d->mOrigMsg) {
0887                     const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
0888                     const QString str = definedLocale.toString(date.date(), QLocale::ShortFormat);
0889                     plainBody.append(str);
0890                     const QString body = plainTextToHtml(str);
0891                     htmlBody.append(body);
0892                 }
0893             } else if (cmd.startsWith(QLatin1StringView("ODATE"))) {
0894                 qCDebug(TEMPLATEPARSER_LOG) << "Command: ODATE";
0895                 i += strlen("ODATE");
0896                 if (d->mOrigMsg) {
0897                     const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
0898                     const QString str = definedLocale.toString(date.date(), QLocale::LongFormat);
0899                     plainBody.append(str);
0900                     const QString body = plainTextToHtml(str);
0901                     htmlBody.append(body);
0902                 }
0903             } else if (cmd.startsWith(QLatin1StringView("ODOW"))) {
0904                 qCDebug(TEMPLATEPARSER_LOG) << "Command: ODOW";
0905                 i += strlen("ODOW");
0906                 if (d->mOrigMsg) {
0907                     const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
0908                     const QString str = definedLocale.dayName(date.date().dayOfWeek(), QLocale::LongFormat);
0909                     plainBody.append(str);
0910                     const QString body = plainTextToHtml(str);
0911                     htmlBody.append(body);
0912                 }
0913             } else if (cmd.startsWith(QLatin1StringView("OTIMELONGEN"))) {
0914                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTIMELONGEN";
0915                 i += strlen("OTIMELONGEN");
0916                 if (d->mOrigMsg) {
0917                     const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
0918                     QLocale locale(QLocale::C);
0919                     const QString str = locale.toString(date.time(), QLocale::LongFormat);
0920                     plainBody.append(str);
0921                     const QString body = plainTextToHtml(str);
0922                     htmlBody.append(body);
0923                 }
0924             } else if (cmd.startsWith(QLatin1StringView("OTIMELONG"))) {
0925                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTIMELONG";
0926                 i += strlen("OTIMELONG");
0927                 if (d->mOrigMsg) {
0928                     const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
0929                     const QString str = definedLocale.toString(date.time(), QLocale::LongFormat);
0930                     plainBody.append(str);
0931                     const QString body = plainTextToHtml(str);
0932                     htmlBody.append(body);
0933                 }
0934             } else if (cmd.startsWith(QLatin1StringView("OTIME"))) {
0935                 qCDebug(TEMPLATEPARSER_LOG) << "Command: OTIME";
0936                 i += strlen("OTIME");
0937                 if (d->mOrigMsg) {
0938                     const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
0939                     const QString str = definedLocale.toString(date.time(), QLocale::ShortFormat);
0940                     plainBody.append(str);
0941                     const QString body = plainTextToHtml(str);
0942                     htmlBody.append(body);
0943                 }
0944             } else if (cmd.startsWith(QLatin1StringView("BLANK"))) {
0945                 // do nothing
0946                 qCDebug(TEMPLATEPARSER_LOG) << "Command: BLANK";
0947                 i += strlen("BLANK");
0948             } else if (cmd.startsWith(QLatin1StringView("NOP"))) {
0949                 // do nothing
0950                 qCDebug(TEMPLATEPARSER_LOG) << "Command: NOP";
0951                 i += strlen("NOP");
0952             } else if (cmd.startsWith(QLatin1StringView("CLEAR"))) {
0953                 // clear body buffer; not too useful yet
0954                 qCDebug(TEMPLATEPARSER_LOG) << "Command: CLEAR";
0955                 i += strlen("CLEAR");
0956                 plainBody.clear();
0957                 htmlBody.clear();
0958                 auto header = new KMime::Headers::Generic("X-KMail-CursorPos");
0959                 header->fromUnicodeString(QString::number(0), "utf-8");
0960                 d->mMsg->setHeader(header);
0961             } else if (cmd.startsWith(QLatin1StringView("DEBUGOFF"))) {
0962                 // turn off debug
0963                 qCDebug(TEMPLATEPARSER_LOG) << "Command: DEBUGOFF";
0964                 i += strlen("DEBUGOFF");
0965                 d->mDebug = false;
0966             } else if (cmd.startsWith(QLatin1StringView("DEBUG"))) {
0967                 // turn on debug
0968                 qCDebug(TEMPLATEPARSER_LOG) << "Command: DEBUG";
0969                 i += strlen("DEBUG");
0970                 d->mDebug = true;
0971             } else if (cmd.startsWith(QLatin1StringView("CURSOR"))) {
0972                 // turn on debug
0973                 qCDebug(TEMPLATEPARSER_LOG) << "Command: CURSOR";
0974                 int oldI = i;
0975                 i += strlen("CURSOR");
0976                 auto header = new KMime::Headers::Generic("X-KMail-CursorPos");
0977                 header->fromUnicodeString(QString::number(plainBody.length()), "utf-8");
0978                 /* if template is:
0979                  *  FOOBAR
0980                  *  %CURSOR
0981                  *
0982                  * Make sure there is an empty line for the cursor otherwise it will be placed at the end of FOOBAR
0983                  */
0984                 if (oldI > 0 && tmpl[oldI - 1] == QLatin1Char('\n') && i == tmpl_len - 1) {
0985                     plainBody.append(QLatin1Char('\n'));
0986                 }
0987                 d->mMsg->setHeader(header);
0988                 d->mForceCursorPosition = true;
0989                 // FIXME HTML part for header remaining
0990             } else if (cmd.startsWith(QLatin1StringView("SIGNATURE"))) {
0991                 qCDebug(TEMPLATEPARSER_LOG) << "Command: SIGNATURE";
0992                 i += strlen("SIGNATURE");
0993                 plainBody.append(getPlainSignature());
0994                 htmlBody.append(getHtmlSignature());
0995             } else {
0996                 // wrong command, do nothing
0997                 plainBody.append(c);
0998                 htmlBody.append(c);
0999             }
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 
1044 QString 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.
1064 QString 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 
1080 void TemplateParserJob::addProcessedBodyToMessage(const QString &plainBody, const QString &htmlBody) const
1081 {
1082     MessageCore::ImageCollector ic;
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, "utf-8");
1096     }
1097 
1098     if (!d->mCC.isEmpty()) {
1099         d->mMsg->cc()->fromUnicodeString(d->mMsg->cc()->asUnicodeString() + QLatin1Char(',') + d->mCC, "utf-8");
1100     }
1101 
1102     d->mMsg->contentType()->clear(); // 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 
1133 KMime::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(QStringLiteral("name")) && !ct->hasParameter(QStringLiteral("filename"))) {
1150                 ct->setParameter(QStringLiteral("name"), i18nc("@item:intext", "Attachment %1", attachmentNumber));
1151             }
1152         }
1153         ++attachmentNumber;
1154     }
1155     return mixedPart;
1156 }
1157 
1158 KMime::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 
1174 KMime::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 
1185 KMime::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 
1205 QString 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 
1218 QString 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 
1319 QString TemplateParserJob::pipe(const QString &cmd, const QString &buf)
1320 {
1321     KProcess process;
1322     bool success;
1323 
1324     process.setOutputChannelMode(KProcess::SeparateChannels);
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) {
1361         QStringDecoder codecFromName(QStringEncoder::System);
1362         return codecFromName.decode(process.readAllStandardOutput());
1363     } else {
1364         return {};
1365     }
1366 }
1367 
1368 void TemplateParserJob::setWordWrap(bool wrap, int wrapColWidth)
1369 {
1370     d->mWrap = wrap;
1371     d->mColWrap = wrapColWidth;
1372 }
1373 
1374 QString 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) {
1389         result = MessageCore::StringUtil::stripSignature(result);
1390     }
1391 
1392     return result;
1393 }
1394 
1395 QString 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 
1418 QString 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 
1433 QString 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 
1447 uint 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 
1463 bool 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 
1476 QString 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 
1484 void 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"