Messagelib

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

KDE's Doxygen guidelines are available online.