Libksieve

vacationutils.cpp
1/*
2 SPDX-FileCopyrightText: 2013-2024 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "vacationutils.h"
8#include "vacationscriptextractor.h"
9#include <KIdentityManagementCore/Identity>
10#include <KIdentityManagementCore/IdentityManager>
11#include <ksievecore/sieve-vacation.h>
12
13#include <KLocalizedString>
14#include <QDate>
15#include <QLocale>
16#include <QRegularExpression>
17
19using namespace KSieveCore;
20
21static inline QString dotstuff(QString s) // krazy:exclude=passbyvalue
22{
23 if (s.startsWith(QLatin1Char('.'))) {
24 return QLatin1Char('.') + s.replace(QLatin1StringView("\n."), QStringLiteral("\n.."));
25 } else {
26 return s.replace(QLatin1StringView("\n."), QStringLiteral("\n.."));
27 }
28}
29
30static inline QString stringReplace(QString s)
31{
32 static QRegularExpression reg(QStringLiteral("[\n\t]+"));
33 s.replace(reg, QStringLiteral(" "));
34 return s.replace(QLatin1Char('\"'), QStringLiteral("\\\""));
35}
36
37QString VacationUtils::defaultSubject()
38{
39 return i18n("Out of office till %1", QLocale().toString(QDate::currentDate().addDays(1)));
40}
41
42QString KSieveCore::VacationUtils::mailAction(KSieveCore::VacationUtils::MailAction action)
43{
44 switch (action) {
45 case Keep:
46 return i18n("Keep");
47 case Discard:
48 return i18n("Discard");
49 case Sendto:
50 return i18n("Redirect to");
51 case CopyTo:
52 return i18n("Copy to");
53 }
54 Q_UNREACHABLE();
55 return {};
56}
57
58KSieveCore::VacationUtils::MailAction KSieveCore::VacationUtils::defaultMailAction()
59{
60 return KSieveCore::VacationUtils::Keep;
61}
62
63QString KSieveCore::VacationUtils::defaultMessageText()
64{
65 return i18n(
66 "I am out of office till %1.\n"
67 "\n"
68 "In urgent cases, please contact Mrs. \"vacation replacement\"\n"
69 "\n"
70 "email: \"email address of vacation replacement\"\n"
71 "phone: +49 711 1111 11\n"
72 "fax.: +49 711 1111 12\n"
73 "\n"
74 "Yours sincerely,\n"
75 "-- \"enter your name and email address here\"\n",
76 QLocale().toString(QDate::currentDate().addDays(1)));
77}
78
79int VacationUtils::defaultNotificationInterval()
80{
81 return 7; // days
82}
83
84KMime::Types::AddrSpecList VacationUtils::defaultMailAliases()
85{
88 KIdentityManagementCore::IdentityManager::ConstIterator end(manager->end());
89 for (KIdentityManagementCore::IdentityManager::ConstIterator it = manager->begin(); it != end; ++it) {
90 if (!(*it).primaryEmailAddress().isEmpty()) {
92 a.fromUnicodeString((*it).primaryEmailAddress());
93 sl.push_back(a.addrSpec());
94 }
95 const QStringList lstEmails = (*it).emailAliases();
96 for (const QString &email : lstEmails) {
98 a.fromUnicodeString(email);
99 sl.push_back(a.addrSpec());
100 }
101 }
102
103 return sl;
104}
105
106bool VacationUtils::defaultSendForSpam()
107{
108 return KSieveCore::VacationSettings::outOfOfficeReactToSpam();
109}
110
111QString VacationUtils::defaultDomainName()
112{
113 return KSieveCore::VacationSettings::outOfOfficeDomain();
114}
115
116QDate VacationUtils::defaultStartDate()
117{
118 return QDate::currentDate();
119}
120
121QDate VacationUtils::defaultEndDate()
122{
123 return defaultStartDate().addDays(7);
124}
125
126VacationUtils::Vacation VacationUtils::parseScript(const QString &script)
127{
128 KSieveCore::VacationUtils::Vacation vacation;
129 if (script.trimmed().isEmpty()) {
130 vacation.valid = false;
131 vacation.active = false;
132 vacation.mailAction = VacationUtils::defaultMailAction();
133 vacation.messageText = VacationUtils::defaultMessageText();
134 vacation.subject = VacationUtils::defaultSubject();
135 vacation.notificationInterval = VacationUtils::defaultNotificationInterval();
136 vacation.aliases = VacationUtils::defaultMailAliases();
137 vacation.sendForSpam = VacationUtils::defaultSendForSpam();
138 vacation.reactOndomainName = VacationUtils::defaultDomainName();
139 return vacation;
140 }
141
142 // The trimmed() call below prevents parsing errors. The
143 // slave somehow omits the last \n, which results in a lone \r at
144 // the end, leading to a parse error.
145 const QByteArray scriptUTF8 = script.trimmed().toUtf8();
146 qCDebug(LIBKSIEVECORE_LOG) << "scriptUtf8 = \"" + scriptUTF8 + "\"";
147 KSieve::Parser parser(scriptUTF8.begin(), scriptUTF8.begin() + scriptUTF8.length());
148 VacationDataExtractor vdx;
149 SpamDataExtractor sdx;
150 DomainRestrictionDataExtractor drdx;
151 DateExtractor dx;
152 KSieveExt::MultiScriptBuilder tsb(&vdx, &sdx, &drdx, &dx);
153 parser.setScriptBuilder(&tsb);
154 if (!parser.parse() || !vdx.commandFound()) {
155 vacation.active = false;
156 vacation.valid = false;
157 return vacation;
158 }
159 vacation.valid = true;
160 vacation.active = vdx.active();
161 vacation.mailAction = vdx.mailAction();
162 vacation.mailActionRecipient = vdx.mailActionRecipient();
163 vacation.messageText = vdx.messageText().trimmed();
164 if (!vdx.subject().isEmpty()) {
165 vacation.subject = vdx.subject().trimmed();
166 }
167 vacation.notificationInterval = vdx.notificationInterval();
168 vacation.aliases = KMime::Types::AddrSpecList();
169 const QStringList lstAliases = vdx.aliases();
170 for (const QString &alias : lstAliases) {
172 a.fromUnicodeString(alias);
173 vacation.aliases.append(a.addrSpec());
174 }
175
176 if (!vacation.active && !vdx.ifComment().isEmpty()) {
177 const QByteArray newScript = QByteArrayLiteral("if ") + vdx.ifComment().toUtf8() + QByteArrayLiteral("{vacation;}");
178 tsb = KSieveExt::MultiScriptBuilder(&sdx, &drdx, &dx);
179 KSieve::Parser activeScriptParser(newScript.begin(), newScript.begin() + newScript.length());
180 activeScriptParser.setScriptBuilder(&tsb);
181 if (!activeScriptParser.parse()) {
182 vacation.valid = false;
183 return vacation;
184 }
185 }
186
187 vacation.sendForSpam = !sdx.found();
188 vacation.reactOndomainName = drdx.domainName();
189 vacation.startDate = dx.startDate();
190 vacation.startTime = dx.startTime();
191 vacation.endDate = dx.endDate();
192 vacation.endTime = dx.endTime();
193
194 return vacation;
195}
196
197QString KSieveCore::VacationUtils::composeScript(const Vacation &vacation)
198{
199 QStringList condition;
200 QStringList require;
201
202 require << QStringLiteral("vacation");
203
204 if (vacation.startDate.isValid() || vacation.endDate.isValid()) {
205 require << QStringLiteral("date");
206 require << QStringLiteral("relational");
207 }
208
209 if (vacation.startDate.isValid()) {
210 if (vacation.startTime.isValid()) {
211 const QDateTime start(vacation.startDate, vacation.startTime);
212 condition.append(QStringLiteral("currentdate :value \"ge\" \"iso8601\" \"%1\"").arg(start.toString(Qt::ISODate)));
213 } else {
214 condition.append(QStringLiteral("currentdate :value \"ge\" \"date\" \"%1\"").arg(vacation.startDate.toString(Qt::ISODate)));
215 }
216 }
217
218 if (vacation.endDate.isValid()) {
219 if (vacation.endTime.isValid()) {
220 const QDateTime end(vacation.endDate, vacation.endTime);
221 condition.append(QStringLiteral("currentdate :value \"le\" \"iso8601\" \"%1\"").arg(end.toString(Qt::ISODate)));
222 } else {
223 condition.append(QStringLiteral("currentdate :value \"le\" \"date\" \"%1\"").arg(vacation.endDate.toString(Qt::ISODate)));
224 }
225 }
226
227 if (!vacation.sendForSpam) {
228 condition.append(QStringLiteral("not header :contains \"X-Spam-Flag\" \"YES\""));
229 }
230
231 if (!vacation.reactOndomainName.isEmpty()) {
232 condition.append(QStringLiteral("address :domain :contains \"from\" \"%1\"").arg(vacation.reactOndomainName));
233 }
234
235 QString addressesArgument;
236 QStringList aliases;
237 if (!vacation.aliases.empty()) {
238 addressesArgument += QStringLiteral(":addresses [ ");
239 QStringList sl;
240 sl.reserve(vacation.aliases.count());
241 AddrSpecList::const_iterator end = vacation.aliases.constEnd();
242 for (AddrSpecList::const_iterator it = vacation.aliases.begin(); it != end; ++it) {
243 sl.push_back(QLatin1Char('"')
244 + (*it).asString().replace(QLatin1Char('\\'), QStringLiteral("\\\\")).replace(QLatin1Char('"'), QStringLiteral("\\\""))
245 + QLatin1Char('"'));
246 aliases.push_back((*it).asString());
247 }
248 addressesArgument += sl.join(QLatin1StringView(", ")) + QStringLiteral(" ] ");
249 }
250
251 QString sVacation(QStringLiteral("vacation "));
252 sVacation += addressesArgument;
253 if (vacation.notificationInterval > 0) {
254 sVacation += QStringLiteral(":days %1 ").arg(vacation.notificationInterval);
255 }
256
257 if (!vacation.subject.trimmed().isEmpty()) {
258 sVacation += QStringLiteral(":subject \"%1\" ").arg(stringReplace(vacation.subject).trimmed());
259 }
260
261 sVacation += QStringLiteral("text:\n");
262 sVacation += dotstuff(vacation.messageText.isEmpty() ? VacationUtils::defaultMessageText() : vacation.messageText);
263 sVacation += QStringLiteral("\n.\n;");
264
265 switch (vacation.mailAction) {
266 case VacationUtils::Keep:
267 break;
268 case VacationUtils::Discard:
269 sVacation += QStringLiteral("\ndiscard;");
270 break;
271 case VacationUtils::Sendto:
272 sVacation += QLatin1StringView("\nredirect \"") + vacation.mailActionRecipient + QLatin1StringView("\";");
273 break;
274 case VacationUtils::CopyTo:
275 require << QStringLiteral("copy");
276 sVacation += QLatin1StringView("\nredirect :copy \"") + vacation.mailActionRecipient + QLatin1StringView("\";");
277 break;
278 }
279
280 QString script = QStringLiteral("require [\"%1\"];\n\n").arg(require.join(QStringLiteral("\", \"")));
281
282 if (condition.isEmpty()) {
283 if (vacation.active) {
284 script += sVacation;
285 } else {
286 script += QStringLiteral("if false\n{\n\t");
287 script += sVacation;
288 script += QStringLiteral("\n}");
289 }
290 } else {
291 if (vacation.active) {
292 script += QStringLiteral("if allof(%1)\n{\n\t").arg(condition.join(QStringLiteral(", ")));
293 } else {
294 script += QStringLiteral("if false # allof(%1)\n{\n\t").arg(condition.join(QStringLiteral(", ")));
295 }
296 script += sVacation;
297 script += QStringLiteral("\n}");
298 }
299
300 script += QStringLiteral("\n");
301
302 return script;
303}
304
305QString KSieveCore::VacationUtils::mergeRequireLine(const QString &script, const QString &scriptUpdate)
306{
307 const QByteArray scriptUTF8 = script.trimmed().toUtf8();
308 if (scriptUTF8.isEmpty()) {
309 return scriptUpdate;
310 }
311
312 const QByteArray scriptUpdateUTF8 = scriptUpdate.trimmed().toUtf8();
313 if (scriptUpdateUTF8.isEmpty()) {
314 return script;
315 }
316
317 KSieve::Parser parser(scriptUTF8.begin(), scriptUTF8.begin() + scriptUTF8.length());
318 KSieve::Parser parserUpdate(scriptUpdateUTF8.begin(), scriptUpdateUTF8.begin() + scriptUpdateUTF8.length());
319 RequireExtractor rx;
320 RequireExtractor rxUpdate;
321 parser.setScriptBuilder(&rx);
322 parserUpdate.setScriptBuilder(&rxUpdate);
323
324 int insert(0);
325 QStringList lines = script.split(QLatin1Char('\n'));
326 QSet<QString> requirements;
327
328 if (parser.parse() && rx.commandFound()) {
329 insert = rx.lineStart();
330 const int endOld(rx.lineEnd());
331 for (int i = insert; i <= endOld; ++i) {
332 lines.removeAt(insert);
333 }
334 const auto requirementsSet = rx.requirements();
335 requirements = QSet<QString>(requirementsSet.begin(), requirementsSet.end());
336 }
337
338 if (parserUpdate.parse() && rxUpdate.commandFound()) {
339 const auto requirementsSet = rxUpdate.requirements();
340 requirements += QSet<QString>(requirementsSet.begin(), requirementsSet.end());
341 }
342
343 const int requirementscount = requirements.count();
344 if (requirementscount > 1) {
345 QStringList req = requirements.values();
346 req.sort();
347 lines.insert(insert, QStringLiteral("require [\"%1\"];").arg(req.join(QStringLiteral("\", \""))));
348 } else if (requirementscount == 1) {
349 lines.insert(insert, QStringLiteral("require \"%1\";").arg(requirements.values().constFirst()));
350 }
351
352 return lines.join(QLatin1Char('\n'));
353}
354
355QString KSieveCore::VacationUtils::updateVacationBlock(const QString &oldScript, const QString &newScript)
356{
357 const QByteArray oldScriptUTF8 = oldScript.trimmed().toUtf8();
358 if (oldScriptUTF8.isEmpty()) {
359 return newScript;
360 }
361
362 const QByteArray newScriptUTF8 = newScript.trimmed().toUtf8();
363 if (newScriptUTF8.isEmpty()) {
364 return oldScript;
365 }
366
367 KSieve::Parser parserOld(oldScriptUTF8.begin(), oldScriptUTF8.begin() + oldScriptUTF8.length());
368 KSieve::Parser parserNew(newScriptUTF8.begin(), newScriptUTF8.begin() + newScriptUTF8.length());
369 VacationDataExtractor vdxOld;
370 VacationDataExtractor vdxNew;
371 RequireExtractor rx;
372 KSieveExt::MultiScriptBuilder tsb(&vdxOld, &rx);
373 parserOld.setScriptBuilder(&tsb);
374 parserNew.setScriptBuilder(&vdxNew);
375
376 int startOld(0);
377
378 QStringList lines = oldScript.split(QLatin1Char('\n'));
379
380 if (parserOld.parse() && vdxOld.commandFound()) {
381 startOld = vdxOld.lineStart();
382 const int endOld(vdxOld.lineEnd());
383 for (int i = startOld; i <= endOld; ++i) {
384 lines.removeAt(startOld);
385 }
386 } else {
387 if (rx.commandFound()) { // after require
388 startOld = rx.lineEnd() + 1;
389 } else {
390 startOld = 0;
391 }
392 }
393
394 if (parserNew.parse() && vdxNew.commandFound()) {
395 const int startNew(vdxNew.lineStart());
396 const int endNew(vdxNew.lineEnd());
397 QStringList linesNew = newScript.split(QLatin1Char('\n'));
398 for (int i = endNew; i >= startNew; --i) {
399 lines.insert(startOld, linesNew.at(i));
400 }
401 }
402
403 return lines.join(QLatin1Char('\n'));
404}
static IdentityManager * self()
void fromUnicodeString(const QString &s)
Parser for the Sieve grammar.
Definition parser.h:24
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
KGuiItem insert()
const QList< QKeySequence > & end()
iterator begin()
bool isEmpty() const const
qsizetype length() const const
QDate addDays(qint64 ndays) const const
QDate currentDate()
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
const T & constFirst() const const
iterator insert(const_iterator before, parameter_type value)
bool isEmpty() const const
void push_back(parameter_type value)
void removeAt(qsizetype i)
void reserve(qsizetype size)
qsizetype count() const const
QList< T > values() const const
QString arg(Args &&... args) const const
bool isEmpty() const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toUtf8() const const
QString trimmed() const const
QString join(QChar separator) const const
void sort(Qt::CaseSensitivity cs)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:17:19 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.