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 "e) 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"