Libksieve

vacationutils.cpp
1 /*
2  Copyright (C) 2013-2020 Laurent Montel <[email protected]>
3 
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Library General Public
6  License as published by the Free Software Foundation; either
7  version 2 of the License, or (at your option) any later version.
8 
9  This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  Library General Public License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to
16  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  Boston, MA 02110-1301, USA.
18 */
19 
20 #include "vacationutils.h"
21 #include "vacationscriptextractor.h"
22 #include "legacy/vacationutils.h"
23 #include "sieve-vacation.h"
24 #include <KIdentityManagement/IdentityManager>
25 #include <KIdentityManagement/Identity>
26 
27 #include <KLocalizedString>
28 #include <QDate>
29 #include <QLocale>
30 #include <QRegularExpression>
31 
33 using namespace KSieveUi;
34 
35 static inline QString dotstuff(QString s) // krazy:exclude=passbyvalue
36 {
37  if (s.startsWith(QLatin1Char('.'))) {
38  return QLatin1Char('.') + s.replace(QLatin1String("\n."), QStringLiteral("\n.."));
39  } else {
40  return s.replace(QLatin1String("\n."), QStringLiteral("\n.."));
41  }
42 }
43 
44 static inline QString stringReplace(QString s)
45 {
46  s.replace(QRegularExpression(QStringLiteral("[\n\t]+")), QStringLiteral(" "));
47  return s.replace(QLatin1Char('\"'), QStringLiteral("\\\""));
48 }
49 
50 QString VacationUtils::defaultSubject()
51 {
52  return i18n("Out of office till %1", QLocale().toString(QDate::currentDate().addDays(1)));
53 }
54 
55 QString KSieveUi::VacationUtils::mailAction(KSieveUi::VacationUtils::MailAction action)
56 {
57  switch (action) {
58  case Keep:
59  return i18n("Keep");
60  case Discard:
61  return i18n("Discard");
62  case Sendto:
63  return i18n("Redirect to");
64  case CopyTo:
65  return i18n("Copy to");
66  }
67  Q_UNREACHABLE();
68  return {};
69 }
70 
71 KSieveUi::VacationUtils::MailAction KSieveUi::VacationUtils::defaultMailAction()
72 {
73  return KSieveUi::VacationUtils::Keep;
74 }
75 
76 QString KSieveUi::VacationUtils::defaultMessageText()
77 {
78  return i18n("I am out of office till %1.\n"
79  "\n"
80  "In urgent cases, please contact Mrs. \"vacation replacement\"\n"
81  "\n"
82  "email: \"email address of vacation replacement\"\n"
83  "phone: +49 711 1111 11\n"
84  "fax.: +49 711 1111 12\n"
85  "\n"
86  "Yours sincerely,\n"
87  "-- \"enter your name and email address here\"\n",
88  QLocale().toString(QDate::currentDate().addDays(1)));
89 }
90 
91 int VacationUtils::defaultNotificationInterval()
92 {
93  return 7; // days
94 }
95 
96 KMime::Types::AddrSpecList VacationUtils::defaultMailAliases()
97 {
100  KIdentityManagement::IdentityManager::ConstIterator end(manager->end());
101  for (KIdentityManagement::IdentityManager::ConstIterator it = manager->begin(); it != end; ++it) {
102  if (!(*it).primaryEmailAddress().isEmpty()) {
104  a.fromUnicodeString((*it).primaryEmailAddress());
105  sl.push_back(a.addrSpec());
106  }
107  const QStringList lstEmails = (*it).emailAliases();
108  for (const QString &email : lstEmails) {
110  a.fromUnicodeString(email);
111  sl.push_back(a.addrSpec());
112  }
113  }
114 
115  return sl;
116 }
117 
118 bool VacationUtils::defaultSendForSpam()
119 {
120  return VacationSettings::outOfOfficeReactToSpam();
121 }
122 
123 QString VacationUtils::defaultDomainName()
124 {
125  return VacationSettings::outOfOfficeDomain();
126 }
127 
128 QDate VacationUtils::defaultStartDate()
129 {
130  return QDate::currentDate();
131 }
132 
133 QDate VacationUtils::defaultEndDate()
134 {
135  return defaultStartDate().addDays(7);
136 }
137 
138 VacationUtils::Vacation parseScriptLegacy(const QString &script)
139 {
140  KSieveUi::VacationUtils::Vacation vacation;
141  vacation.active = true;
142  vacation.valid = Legacy::VacationUtils::parseScript(script, vacation.messageText,
143  vacation.subject,
144  vacation.notificationInterval, vacation.aliases,
145  vacation.sendForSpam, vacation.reactOndomainName,
146  vacation.startDate, vacation.endDate);
147  return vacation;
148 }
149 
150 VacationUtils::Vacation VacationUtils::parseScript(const QString &script)
151 {
152  KSieveUi::VacationUtils::Vacation vacation;
153  if (script.trimmed().isEmpty()) {
154  vacation.valid = false;
155  vacation.active = false;
156  vacation.mailAction = VacationUtils::defaultMailAction();
157  vacation.messageText = VacationUtils::defaultMessageText();
158  vacation.subject = VacationUtils::defaultSubject();
159  vacation.notificationInterval = VacationUtils::defaultNotificationInterval();
160  vacation.aliases = VacationUtils::defaultMailAliases();
161  vacation.sendForSpam = VacationUtils::defaultSendForSpam();
162  vacation.reactOndomainName = VacationUtils::defaultDomainName();
163  return vacation;
164  }
165 
166  // The trimmed() call below prevents parsing errors. The
167  // slave somehow omits the last \n, which results in a lone \r at
168  // the end, leading to a parse error.
169  const QByteArray scriptUTF8 = script.trimmed().toUtf8();
170  qCDebug(LIBKSIEVE_LOG) << "scriptUtf8 = \"" + scriptUTF8 + "\"";
171  KSieve::Parser parser(scriptUTF8.begin(),
172  scriptUTF8.begin() + scriptUTF8.length());
173  VacationDataExtractor vdx;
174  SpamDataExtractor sdx;
175  DomainRestrictionDataExtractor drdx;
176  DateExtractor dx;
177  KSieveExt::MultiScriptBuilder tsb(&vdx, &sdx, &drdx, &dx);
178  parser.setScriptBuilder(&tsb);
179  if (!parser.parse() || !vdx.commandFound()) {
180  const auto vac = parseScriptLegacy(script);
181  if (vac.isValid()) {
182  return vac;
183  }
184  vacation.active = false;
185  vacation.valid = false;
186  return vacation;
187  }
188  vacation.valid = true;
189  vacation.active = vdx.active();
190  vacation.mailAction = vdx.mailAction();
191  vacation.mailActionRecipient = vdx.mailActionRecipient();
192  vacation.messageText = vdx.messageText().trimmed();
193  if (!vdx.subject().isEmpty()) {
194  vacation.subject = vdx.subject().trimmed();
195  }
196  vacation.notificationInterval = vdx.notificationInterval();
197  vacation.aliases = KMime::Types::AddrSpecList();
198  const QStringList lstAliases = vdx.aliases();
199  for (const QString &alias : lstAliases) {
201  a.fromUnicodeString(alias);
202  vacation.aliases.append(a.addrSpec());
203  }
204 
205  if (!vacation.active && !vdx.ifComment().isEmpty()) {
206  const QByteArray newScript = QByteArrayLiteral("if ") + vdx.ifComment().toUtf8() + QByteArrayLiteral("{vacation;}");
207  tsb = KSieveExt::MultiScriptBuilder(&sdx, &drdx, &dx);
208  KSieve::Parser activeScriptParser(newScript.begin(),
209  newScript.begin() + newScript.length());
210  activeScriptParser.setScriptBuilder(&tsb);
211  if (!activeScriptParser.parse()) {
212  vacation.valid = false;
213  return vacation;
214  }
215  }
216 
217  vacation.sendForSpam = !sdx.found();
218  vacation.reactOndomainName = drdx.domainName();
219  vacation.startDate = dx.startDate();
220  vacation.startTime = dx.startTime();
221  vacation.endDate = dx.endDate();
222  vacation.endTime = dx.endTime();
223 
224  if (vacation.sendForSpam && vacation.reactOndomainName.isEmpty() && !vacation.startDate.isValid() && !vacation.endDate.isValid()) {
225  const auto vac = parseScriptLegacy(script);
226  if (vac.isValid()) {
227  vacation.sendForSpam = vac.sendForSpam;
228  vacation.reactOndomainName = vac.reactOndomainName;
229  vacation.startDate = vac.startDate;
230  vacation.startTime = vac.startTime;
231  vacation.endDate = vac.endDate;
232  vacation.endTime = vac.endTime;
233  }
234  }
235 
236  return vacation;
237 }
238 
239 QString KSieveUi::VacationUtils::composeScript(const Vacation &vacation)
240 {
241  QStringList condition;
242  QStringList require;
243 
244  require << QStringLiteral("vacation");
245 
246  if (vacation.startDate.isValid() || vacation.endDate.isValid()) {
247  require << QStringLiteral("date");
248  require << QStringLiteral("relational");
249  }
250 
251  if (vacation.startDate.isValid()) {
252  if (vacation.startTime.isValid()) {
253  const QDateTime start(vacation.startDate, vacation.startTime);
254  condition.append(QStringLiteral("currentdate :value \"ge\" \"iso8601\" \"%1\"")
255  .arg(start.toString(Qt::ISODate)));
256  } else {
257  condition.append(QStringLiteral("currentdate :value \"ge\" \"date\" \"%1\"")
258  .arg(vacation.startDate.toString(Qt::ISODate)));
259  }
260  }
261 
262  if (vacation.endDate.isValid()) {
263  if (vacation.endTime.isValid()) {
264  const QDateTime end(vacation.endDate, vacation.endTime);
265  condition.append(QStringLiteral("currentdate :value \"le\" \"iso8601\" \"%1\"")
266  .arg(end.toString(Qt::ISODate)));
267  } else {
268  condition.append(QStringLiteral("currentdate :value \"le\" \"date\" \"%1\"")
269  .arg(vacation.endDate.toString(Qt::ISODate)));
270  }
271  }
272 
273  if (!vacation.sendForSpam) {
274  condition.append(QStringLiteral("not header :contains \"X-Spam-Flag\" \"YES\""));
275  }
276 
277  if (!vacation.reactOndomainName.isEmpty()) {
278  condition.append(QStringLiteral("address :domain :contains \"from\" \"%1\"").arg(vacation.reactOndomainName));
279  }
280 
281  QString addressesArgument;
282  QStringList aliases;
283  if (!vacation.aliases.empty()) {
284  addressesArgument += QStringLiteral(":addresses [ ");
285  QStringList sl;
286  sl.reserve(vacation.aliases.count());
287  AddrSpecList::const_iterator end = vacation.aliases.constEnd();
288  for (AddrSpecList::const_iterator it = vacation.aliases.begin(); it != end; ++it) {
289  sl.push_back(QLatin1Char('"') + (*it).asString().replace(QLatin1Char('\\'), QStringLiteral("\\\\")).replace(QLatin1Char('"'), QStringLiteral("\\\"")) + QLatin1Char('"'));
290  aliases.push_back((*it).asString());
291  }
292  addressesArgument += sl.join(QLatin1String(", ")) + QStringLiteral(" ] ");
293  }
294 
295  QString sVacation(QStringLiteral("vacation "));
296  sVacation += addressesArgument;
297  if (vacation.notificationInterval > 0) {
298  sVacation += QStringLiteral(":days %1 ").arg(vacation.notificationInterval);
299  }
300 
301  if (!vacation.subject.trimmed().isEmpty()) {
302  sVacation += QStringLiteral(":subject \"%1\" ").arg(stringReplace(vacation.subject).trimmed());
303  }
304 
305  sVacation += QStringLiteral("text:\n");
306  sVacation += dotstuff(vacation.messageText.isEmpty() ? VacationUtils::defaultMessageText() : vacation.messageText);
307  sVacation += QStringLiteral("\n.\n;");
308 
309  switch (vacation.mailAction) {
310  case VacationUtils::Keep:
311  break;
312  case VacationUtils::Discard:
313  sVacation += QStringLiteral("\ndiscard;");
314  break;
315  case VacationUtils::Sendto:
316  sVacation += QLatin1String("\nredirect \"") + vacation.mailActionRecipient + QLatin1String("\";");
317  break;
318  case VacationUtils::CopyTo:
319  require << QStringLiteral("copy");
320  sVacation += QLatin1String("\nredirect :copy \"") + vacation.mailActionRecipient + QLatin1String("\";");
321  break;
322  }
323 
324  QString script = QStringLiteral("require [\"%1\"];\n\n").arg(require.join(QStringLiteral("\", \"")));
325 
326  if (condition.isEmpty()) {
327  if (vacation.active) {
328  script += sVacation;
329  } else {
330  script += QStringLiteral("if false\n{\n\t");
331  script += sVacation;
332  script += QStringLiteral("\n}");
333  }
334  } else {
335  if (vacation.active) {
336  script += QStringLiteral("if allof(%1)\n{\n\t").arg(condition.join(QStringLiteral(", ")));
337  } else {
338  script += QStringLiteral("if false # allof(%1)\n{\n\t").arg(condition.join(QStringLiteral(", ")));
339  }
340  script += sVacation;
341  script += QStringLiteral("\n}");
342  }
343 
344  script += QStringLiteral("\n");
345 
346  return script;
347 }
348 
349 QString KSieveUi::VacationUtils::mergeRequireLine(const QString &script, const QString &scriptUpdate)
350 {
351  const QByteArray scriptUTF8 = script.trimmed().toUtf8();
352  if (scriptUTF8.isEmpty()) {
353  return scriptUpdate;
354  }
355 
356  const QByteArray scriptUpdateUTF8 = scriptUpdate.trimmed().toUtf8();
357  if (scriptUpdateUTF8.isEmpty()) {
358  return script;
359  }
360 
361  KSieve::Parser parser(scriptUTF8.begin(),
362  scriptUTF8.begin() + scriptUTF8.length());
363  KSieve::Parser parserUpdate(scriptUpdateUTF8.begin(),
364  scriptUpdateUTF8.begin() + scriptUpdateUTF8.length());
365  RequireExtractor rx, rxUpdate;
366  parser.setScriptBuilder(&rx);
367  parserUpdate.setScriptBuilder(&rxUpdate);
368 
369  int insert(0);
370  QStringList lines = script.split(QLatin1Char('\n'));
371  QSet<QString> requirements;
372 
373  if (parser.parse() && rx.commandFound()) {
374  insert = rx.lineStart();
375  const int endOld(rx.lineEnd());
376  for (int i = insert; i <= endOld; ++i) {
377  lines.removeAt(insert);
378  }
379 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
380  requirements = rx.requirements().toSet();
381 #else
382  const auto requirementsSet = rx.requirements();
383  requirements = QSet<QString>(requirementsSet.begin(), requirementsSet.end());
384 #endif
385  }
386 
387  if (parserUpdate.parse() && rxUpdate.commandFound()) {
388 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
389  requirements += rxUpdate.requirements().toSet();
390 #else
391  const auto requirementsSet = rxUpdate.requirements();
392  requirements += QSet<QString>(requirementsSet.begin(), requirementsSet.end());
393 #endif
394  }
395 
396  const int requirementscount = requirements.count();
397  if (requirementscount > 1) {
398  QStringList req = requirements.values();
399  req.sort();
400  lines.insert(insert, QStringLiteral("require [\"%1\"];").arg(req.join(QStringLiteral("\", \""))));
401  } else if (requirementscount == 1) {
402  lines.insert(insert, QStringLiteral("require \"%1\";").arg(requirements.values().constFirst()));
403  }
404 
405  return lines.join(QLatin1Char('\n'));
406 }
407 
408 QString KSieveUi::VacationUtils::updateVacationBlock(const QString &oldScript, const QString &newScript)
409 {
410  const QByteArray oldScriptUTF8 = oldScript.trimmed().toUtf8();
411  if (oldScriptUTF8.isEmpty()) {
412  return newScript;
413  }
414 
415  const QByteArray newScriptUTF8 = newScript.trimmed().toUtf8();
416  if (newScriptUTF8.isEmpty()) {
417  return oldScript;
418  }
419 
420  KSieve::Parser parserOld(oldScriptUTF8.begin(),
421  oldScriptUTF8.begin() + oldScriptUTF8.length());
422  KSieve::Parser parserNew(newScriptUTF8.begin(),
423  newScriptUTF8.begin() + newScriptUTF8.length());
424  VacationDataExtractor vdxOld, vdxNew;
425  RequireExtractor rx;
426  KSieveExt::MultiScriptBuilder tsb(&vdxOld, &rx);
427  parserOld.setScriptBuilder(&tsb);
428  parserNew.setScriptBuilder(&vdxNew);
429 
430  int startOld(0);
431 
432  QStringList lines = oldScript.split(QLatin1Char('\n'));
433 
434  if (parserOld.parse() && vdxOld.commandFound()) {
435  startOld = vdxOld.lineStart();
436  const int endOld(vdxOld.lineEnd());
437  for (int i = startOld; i <= endOld; ++i) {
438  lines.removeAt(startOld);
439  }
440  } else {
441  if (rx.commandFound()) { // after require
442  startOld = rx.lineEnd() + 1;
443  } else {
444  startOld = 0;
445  }
446  }
447 
448  if (parserNew.parse() && vdxNew.commandFound()) {
449  const int startNew(vdxNew.lineStart());
450  const int endNew(vdxNew.lineEnd());
451  QStringList linesNew = newScript.split(QLatin1Char('\n'));
452  for (int i = endNew; i >= startNew; --i) {
453  lines.insert(startOld, linesNew.at(i));
454  }
455  }
456 
457  return lines.join(QLatin1Char('\n'));
458 }
void push_back(const T &value)
void reserve(int alloc)
const T & constFirst() const const
void removeAt(int i)
bool isEmpty() const const
QString join(const QString &separator) const const
int length() const const
bool insert(Part *part, qint64 *insertId=nullptr)
void append(const T &value)
QList< T > values() const const
void fromUnicodeString(const QString &s)
bool isEmpty() const const
bool isEmpty() const const
QString trimmed() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QByteArray::iterator begin()
int count() const const
QString i18n(const char *text, const TYPE &arg...)
QString & replace(int position, int n, QChar after)
const QList< QKeySequence > & end()
char * toString(const T &value)
void insert(int i, const T &value)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QDate currentDate()
void push_back(const T &value)
void sort(Qt::CaseSensitivity cs)
static IdentityManager * self()
QDate addDays(qint64 ndays) const const
Parser for the Sieve grammar.
Definition: ksieve/parser.h:45
QByteArray toUtf8() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Thu Jul 9 2020 23:07:45 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.