Mailcommon

filterimporterthunderbird.cpp
1/*
2 SPDX-FileCopyrightText: 2011-2024 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "filterimporterthunderbird.h"
8#include "filter/mailfilter.h"
9#include "mailcommon_debug.h"
10#include <MailImporter/FilterIcedove>
11#include <MailImporter/FilterSeaMonkey>
12#include <MailImporter/FilterThunderbird>
13#include <QUrl>
14
15#include <QFile>
16
17using namespace MailCommon;
18
19FilterImporterThunderbird::FilterImporterThunderbird(QFile *file, bool interactive)
20 : FilterImporterAbstract(interactive)
21{
22 QTextStream stream(file);
23 readStream(stream);
24}
25
26FilterImporterThunderbird::FilterImporterThunderbird(QString string, bool interactive)
27 : FilterImporterAbstract(interactive)
28{
29 QTextStream stream(&string);
30 readStream(stream);
31}
32
33FilterImporterThunderbird::~FilterImporterThunderbird() = default;
34
35void FilterImporterThunderbird::readStream(QTextStream &stream)
36{
37 MailFilter *filter = nullptr;
38 while (!stream.atEnd()) {
39 QString line = stream.readLine();
40 qCDebug(MAILCOMMON_LOG) << " line :" << line << " filter " << filter;
41 filter = parseLine(stream, line, filter);
42 }
43 // TODO show limit of action/condition
44 appendFilter(filter);
45}
46
47QString FilterImporterThunderbird::defaultSeaMonkeyFiltersSettingsPath()
48{
49 return MailImporter::FilterSeaMonkey::defaultSettingsPath();
50}
51
52QString FilterImporterThunderbird::defaultIcedoveFiltersSettingsPath()
53{
54 return MailImporter::FilterIcedove::defaultSettingsPath();
55}
56
57QString FilterImporterThunderbird::defaultThunderbirdFiltersSettingsPath()
58{
59 return MailImporter::FilterThunderbird::defaultSettingsPath();
60}
61
62MailCommon::MailFilter *FilterImporterThunderbird::parseLine(QTextStream &stream, QString line, MailCommon::MailFilter *filter)
63{
64 if (line.startsWith(QLatin1StringView("name="))) {
65 appendFilter(filter);
66 filter = new MailFilter();
67 line = cleanArgument(line, QStringLiteral("name="));
68 filter->pattern()->setName(line);
69 filter->setToolbarName(line);
70 } else if (line.startsWith(QLatin1StringView("action="))) {
71 line = cleanArgument(line, QStringLiteral("action="));
72 QString value;
73 QString actionName = extractActions(line, filter, value);
74 if (!stream.atEnd()) {
75 line = stream.readLine();
76 if (line.startsWith(QLatin1StringView("actionValue="))) {
77 value = cleanArgument(line, QStringLiteral("actionValue="));
78 // change priority
79 if (actionName == QLatin1StringView("Change priority")) {
80 QStringList lstValue;
81 lstValue << QStringLiteral("X-Priority");
82 if (value == QLatin1StringView("Highest")) {
83 value = QStringLiteral("1 (Highest)");
84 } else if (value == QLatin1StringView("High")) {
85 value = QStringLiteral("2 (High)");
86 } else if (value == QLatin1StringView("Normal")) {
87 value = QStringLiteral("3 (Normal)");
88 } else if (value == QLatin1StringView("Low")) {
89 value = QStringLiteral("4 (Low)");
90 } else if (value == QLatin1StringView("Lowest")) {
91 value = QStringLiteral("5 (Lowest)");
92 }
93 lstValue << value;
94 value = lstValue.join(QLatin1Char('\t'));
95 actionName = QStringLiteral("add header");
96 } else if (actionName == QLatin1StringView("copy") || actionName == QLatin1StringView("transfer")) {
97 QUrl url = QUrl::fromLocalFile(value);
98 if (url.isValid()) {
99 QString path = url.path();
100 if (path.startsWith(QLatin1Char('/'))) {
101 path.remove(0, 1); // Remove '/'
102 }
103 value = path;
104 }
105 }
106 createFilterAction(filter, actionName, value);
107 } else {
108 createFilterAction(filter, actionName, value);
109 filter = parseLine(stream, line, filter);
110 }
111 } else {
112 createFilterAction(filter, actionName, value);
113 }
114 } else if (line.startsWith(QLatin1StringView("enabled="))) {
115 line = cleanArgument(line, QStringLiteral("enabled="));
116 if (line == QLatin1StringView("no")) {
117 filter->setEnabled(false);
118 }
119 } else if (line.startsWith(QLatin1StringView("condition="))) {
120 line = cleanArgument(line, QStringLiteral("condition="));
121 extractConditions(line, filter);
122 } else if (line.startsWith(QLatin1StringView("type="))) {
123 line = cleanArgument(line, QStringLiteral("type="));
124 extractType(line, filter);
125 } else if (line.startsWith(QLatin1StringView("version="))) {
126 line = cleanArgument(line, QStringLiteral("version="));
127 if (line.toInt() != 9) {
128 qCDebug(MAILCOMMON_LOG) << " thunderbird filter version different of 9 need to look at if it changed";
129 }
130 } else if (line.startsWith(QLatin1StringView("logging="))) {
131 line = cleanArgument(line, QStringLiteral("logging="));
132 if (line == QLatin1StringView("no")) {
133 // TODO
134 } else if (line == QLatin1StringView("yes")) {
135 // TODO
136 } else {
137 qCDebug(MAILCOMMON_LOG) << " Logging option not implemented " << line;
138 }
139 } else {
140 qCDebug(MAILCOMMON_LOG) << "unknown tag : " << line;
141 }
142 return filter;
143}
144
145void FilterImporterThunderbird::extractConditions(const QString &line, MailCommon::MailFilter *filter)
146{
147 if (line.startsWith(QLatin1StringView("AND"))) {
148 filter->pattern()->setOp(SearchPattern::OpAnd);
149 const QStringList conditionsList = line.split(QStringLiteral("AND "));
150 const int numberOfCond(conditionsList.count());
151 for (int i = 0; i < numberOfCond; ++i) {
152 if (!conditionsList.at(i).trimmed().isEmpty()) {
153 splitConditions(conditionsList.at(i), filter);
154 }
155 }
156 } else if (line.startsWith(QLatin1StringView("OR"))) {
157 filter->pattern()->setOp(SearchPattern::OpOr);
158 const QStringList conditionsList = line.split(QStringLiteral("OR "));
159 const int numberOfCond(conditionsList.count());
160 for (int i = 0; i < numberOfCond; ++i) {
161 if (!conditionsList.at(i).trimmed().isEmpty()) {
162 splitConditions(conditionsList.at(i), filter);
163 }
164 }
165 } else if (line.startsWith(QLatin1StringView("ALL"))) {
166 filter->pattern()->setOp(SearchPattern::OpAll);
167 } else {
168 qCDebug(MAILCOMMON_LOG) << " missing extract condition" << line;
169 }
170}
171
172bool FilterImporterThunderbird::splitConditions(const QString &cond, MailCommon::MailFilter *filter)
173{
174 /*
175 * {nsMsgSearchAttrib::Subject, "subject"},
176 {nsMsgSearchAttrib::Sender, "from"},
177 {nsMsgSearchAttrib::Body, "body"},
178 {nsMsgSearchAttrib::Date, "date"},
179 {nsMsgSearchAttrib::Priority, "priority"},
180 {nsMsgSearchAttrib::MsgStatus, "status"},
181 {nsMsgSearchAttrib::To, "to"},
182 {nsMsgSearchAttrib::CC, "cc"},
183 {nsMsgSearchAttrib::ToOrCC, "to or cc"},
184 {nsMsgSearchAttrib::AllAddresses, "all addresses"},
185 {nsMsgSearchAttrib::AgeInDays, "age in days"},
186 {nsMsgSearchAttrib::Label, "label"},
187 {nsMsgSearchAttrib::Keywords, "tag"},
188 {nsMsgSearchAttrib::Size, "size"},
189 // this used to be nsMsgSearchAttrib::SenderInAddressBook
190 // we used to have two Sender menuitems
191 // for backward compatibility, we can still parse
192 // the old style. see bug #179803
193 {nsMsgSearchAttrib::Sender, "from in ab"},
194 {nsMsgSearchAttrib::JunkStatus, "junk status"},
195 {nsMsgSearchAttrib::JunkPercent, "junk percent"},
196 {nsMsgSearchAttrib::JunkScoreOrigin, "junk score origin"},
197 {nsMsgSearchAttrib::HasAttachmentStatus, "has attachment status"},
198
199 */
200
201 QString str = cond.trimmed();
202 str.remove(QLatin1Char('('));
203 str.remove(str.length() - 1, 1); // remove last )
204
205 const QStringList listOfCond = str.split(QLatin1Char(','));
206 if (listOfCond.count() < 3) {
207 qCDebug(MAILCOMMON_LOG) << "We have a pb in cond:" << cond;
208 return false;
209 }
210 const QString field = listOfCond.at(0);
211 const QString function = listOfCond.at(1);
212 const QString contents = listOfCond.at(2);
213
214 QByteArray fieldName;
215 if (field == QLatin1StringView("subject")) {
216 fieldName = "subject";
217 } else if (field == QLatin1StringView("from")) {
218 fieldName = "from";
219 } else if (field == QLatin1StringView("body")) {
220 fieldName = "<body>";
221 } else if (field == QLatin1StringView("date")) {
222 fieldName = "<date>";
223 } else if (field == QLatin1StringView("priority")) {
224 // TODO
225 } else if (field == QLatin1StringView("status")) {
226 fieldName = "<status>";
227 } else if (field == QLatin1StringView("to")) {
228 fieldName = "to";
229 } else if (field == QLatin1StringView("cc")) {
230 fieldName = "cc";
231 } else if (field == QLatin1StringView("to or cc")) {
232 fieldName = "<recipients>";
233 } else if (field == QLatin1StringView("all addresses")) {
234 fieldName = "<recipients>";
235 } else if (field == QLatin1StringView("age in days")) {
236 fieldName = "<age in days>";
237 } else if (field == QLatin1StringView("label")) {
238 // TODO
239 } else if (field == QLatin1StringView("tag")) {
240 fieldName = "<tag>";
241 } else if (field == QLatin1StringView("size")) {
242 fieldName = "<size>";
243 } else if (field == QLatin1StringView("from in ab")) {
244 // TODO
245 } else if (field == QLatin1StringView("junk status")) {
246 // TODO
247 } else if (field == QLatin1StringView("junk percent")) {
248 // TODO
249 } else if (field == QLatin1StringView("junk score origin")) {
250 // TODO
251 } else if (field == QLatin1StringView("has attachment status")) {
252 // TODO
253 }
254
255 if (fieldName.isEmpty()) {
256 qCDebug(MAILCOMMON_LOG) << " Field not implemented: " << field;
257 }
258 /*
259 {nsMsgSearchOp::Contains, "contains"},
260 {nsMsgSearchOp::DoesntContain,"doesn't contain"},
261 {nsMsgSearchOp::Is,"is"},
262 {nsMsgSearchOp::Isnt, "isn't"},
263 {nsMsgSearchOp::IsEmpty, "is empty"},
264 {nsMsgSearchOp::IsntEmpty, "isn't empty"},
265 {nsMsgSearchOp::IsBefore, "is before"},
266 {nsMsgSearchOp::IsAfter, "is after"},
267 {nsMsgSearchOp::IsHigherThan, "is higher than"},
268 {nsMsgSearchOp::IsLowerThan, "is lower than"},
269 {nsMsgSearchOp::BeginsWith, "begins with"},
270 {nsMsgSearchOp::EndsWith, "ends with"},
271 {nsMsgSearchOp::IsInAB, "is in ab"},
272 {nsMsgSearchOp::IsntInAB, "isn't in ab"},
273 {nsMsgSearchOp::IsGreaterThan, "is greater than"},
274 {nsMsgSearchOp::IsLessThan, "is less than"},
275 {nsMsgSearchOp::Matches, "matches"},
276 {nsMsgSearchOp::DoesntMatch, "doesn't match"}
277 */
278 SearchRule::Function functionName = SearchRule::FuncNone;
279
280 if (function == QLatin1StringView("contains")) {
281 functionName = SearchRule::FuncContains;
282 } else if (function == QLatin1StringView("doesn't contain")) {
283 functionName = SearchRule::FuncContainsNot;
284 } else if (function == QLatin1StringView("is")) {
285 functionName = SearchRule::FuncEquals;
286 } else if (function == QLatin1StringView("isn't")) {
287 functionName = SearchRule::FuncNotEqual;
288 } else if (function == QLatin1StringView("is empty")) {
289 // TODO
290 } else if (function == QLatin1StringView("isn't empty")) {
291 // TODO
292 } else if (function == QLatin1StringView("is before")) {
293 functionName = SearchRule::FuncIsLess;
294 } else if (function == QLatin1StringView("is after")) {
295 functionName = SearchRule::FuncIsGreater;
296 } else if (function == QLatin1StringView("is higher than")) {
297 functionName = SearchRule::FuncIsGreater;
298 } else if (function == QLatin1StringView("is lower than")) {
299 functionName = SearchRule::FuncIsLess;
300 } else if (function == QLatin1StringView("begins with")) {
301 functionName = SearchRule::FuncStartWith;
302 } else if (function == QLatin1StringView("ends with")) {
303 functionName = SearchRule::FuncEndWith;
304 } else if (function == QLatin1StringView("is in ab")) {
305 functionName = SearchRule::FuncIsInAddressbook;
306 } else if (function == QLatin1StringView("isn't in ab")) {
307 functionName = SearchRule::FuncIsNotInAddressbook;
308 } else if (function == QLatin1StringView("is greater than")) {
309 functionName = SearchRule::FuncIsGreater;
310 } else if (function == QLatin1StringView("is less than")) {
311 functionName = SearchRule::FuncIsLess;
312 } else if (function == QLatin1StringView("matches")) {
313 functionName = SearchRule::FuncEquals;
314 } else if (function == QLatin1StringView("doesn't match")) {
315 functionName = SearchRule::FuncNotEqual;
316 }
317
318 if (functionName == SearchRule::FuncNone) {
319 qCDebug(MAILCOMMON_LOG) << " functionName not implemented: " << function;
320 }
321 QString contentsName;
322 if (fieldName == "<status>") {
323 if (contents == QLatin1StringView("read")) {
324 contentsName = QStringLiteral("Read");
325 } else if (contents == QLatin1StringView("unread")) {
326 contentsName = QStringLiteral("Unread");
327 } else if (contents == QLatin1StringView("new")) {
328 contentsName = QStringLiteral("New");
329 } else if (contents == QLatin1StringView("forwarded")) {
330 contentsName = QStringLiteral("Forwarded");
331 } else {
332 qCDebug(MAILCOMMON_LOG) << " contents for status not implemented " << contents;
333 }
334 } else if (fieldName == "<size>") {
335 int value = contents.toInt();
336 value = value * 1024; // Ko
337 contentsName = QString::number(value);
338 } else if (fieldName == "<date>") {
339 QLocale locale(QLocale::C);
340 const QDate date = locale.toDate(contents, QStringLiteral("dd-MMM-yyyy"));
341 contentsName = date.toString(Qt::ISODate);
342 } else {
343 contentsName = contents;
344 }
345
346 SearchRule::Ptr rule = SearchRule::createInstance(fieldName, functionName, contentsName);
347 filter->pattern()->append(rule);
348 // qCDebug(MAILCOMMON_LOG) << " field :" << field << " function :" << function
349 // << " contents :" << contents << " cond :" << cond;
350 return true;
351}
352
353QString FilterImporterThunderbird::extractActions(const QString &line, MailCommon::MailFilter *filter, QString &value)
354{
355 /*
356 { nsMsgFilterAction::MoveToFolder, "Move to folder"},
357 { nsMsgFilterAction::CopyToFolder, "Copy to folder"},
358 { nsMsgFilterAction::ChangePriority, "Change priority"},
359 { nsMsgFilterAction::Delete, "Delete"},
360 { nsMsgFilterAction::MarkRead, "Mark read"},
361 { nsMsgFilterAction::KillThread, "Ignore thread"},
362 { nsMsgFilterAction::KillSubthread, "Ignore subthread"},
363 { nsMsgFilterAction::WatchThread, "Watch thread"},
364 { nsMsgFilterAction::MarkFlagged, "Mark flagged"},
365 { nsMsgFilterAction::Label, "Label"},
366 { nsMsgFilterAction::Reply, "Reply"},
367 { nsMsgFilterAction::Forward, "Forward"},
368 { nsMsgFilterAction::StopExecution, "Stop execution"},
369 { nsMsgFilterAction::DeleteFromPop3Server, "Delete from Pop3 server"},
370 { nsMsgFilterAction::LeaveOnPop3Server, "Leave on Pop3 server"},
371 { nsMsgFilterAction::JunkScore, "JunkScore"},
372 { nsMsgFilterAction::FetchBodyFromPop3Server, "Fetch body from Pop3Server"},
373 { nsMsgFilterAction::AddTag, "AddTag"},
374 { nsMsgFilterAction::Custom, "Custom"},
375 */
376
377 QString actionName;
378 if (line == QLatin1StringView("Move to folder")) {
379 actionName = QStringLiteral("transfer");
380 } else if (line == QLatin1StringView("Forward")) {
381 actionName = QStringLiteral("forward");
382 } else if (line == QLatin1StringView("Mark read")) {
383 actionName = QStringLiteral("set status");
384 value = QStringLiteral("R");
385 } else if (line == QLatin1StringView("Mark unread")) {
386 actionName = QStringLiteral("set status");
387 value = QStringLiteral("U"); // TODO verify
388 } else if (line == QLatin1StringView("Copy to folder")) {
389 actionName = QStringLiteral("copy");
390 } else if (line == QLatin1StringView("AddTag")) {
391 actionName = QStringLiteral("add tag");
392 } else if (line == QLatin1StringView("Delete")) {
393 actionName = QStringLiteral("delete");
394 } else if (line == QLatin1StringView("Change priority")) {
395 actionName = QStringLiteral("Change priority"); // Doesn't exist in kmail but we help us to importing
396 } else if (line == QLatin1StringView("Ignore thread")) {
397 } else if (line == QLatin1StringView("Ignore subthread")) {
398 } else if (line == QLatin1StringView("Watch thread")) {
399 } else if (line == QLatin1StringView("Mark flagged")) {
400 } else if (line == QLatin1StringView("Label")) {
401 } else if (line == QLatin1StringView("Reply")) {
402 actionName = QStringLiteral("set Reply-To");
403 } else if (line == QLatin1StringView("Stop execution")) {
404 filter->setStopProcessingHere(true);
405 return {};
406 } else if (line == QLatin1StringView("Delete from Pop3 server")) {
407 } else if (line == QLatin1StringView("JunkScore")) {
408 } else if (line == QLatin1StringView("Fetch body from Pop3Server")) {
409 } else if (line == QLatin1StringView("Custom")) {
410 }
411 if (actionName.isEmpty()) {
412 qCDebug(MAILCOMMON_LOG) << QStringLiteral(" missing convert method: %1").arg(line);
413 }
414 return actionName;
415}
416
417void FilterImporterThunderbird::extractType(const QString &line, MailCommon::MailFilter *filter)
418{
419 const int value = line.toInt();
420 if (value == 1) {
421 filter->setApplyOnInbound(true);
422 filter->setApplyOnExplicit(false);
423 // Checking mail
424 } else if (value == 16) {
425 filter->setApplyOnInbound(false);
426 filter->setApplyOnExplicit(true);
427 // Manual mail
428 } else if (value == 17) {
429 filter->setApplyOnInbound(true);
430 filter->setApplyOnExplicit(true);
431 // Checking mail or manual
432 } else if (value == 32) {
433 filter->setApplyOnExplicit(false);
434 filter->setApplyOnOutbound(true);
435 filter->setApplyOnInbound(false);
436 // checking mail after classification
437 } else if (value == 48) {
438 filter->setApplyOnExplicit(true);
439 filter->setApplyOnOutbound(true);
440 filter->setApplyOnInbound(false);
441 // checking mail after classification or manual check
442 } else {
443 qCDebug(MAILCOMMON_LOG) << " type value is not valid :" << value;
444 }
445}
446
447QString FilterImporterThunderbird::cleanArgument(const QString &line, const QString &removeStr)
448{
449 QString str = line;
450 str.remove(removeStr);
451 str.remove(QStringLiteral("\""));
452 str.remove(str.length(), 1); // remove last "
453 return str;
454}
The MailFilter class.
Definition mailfilter.h:29
std::shared_ptr< SearchRule > Ptr
Defines a pointer to a search rule.
Definition searchrule.h:29
Function
Describes operators for comparison of field and contents.
Definition searchrule.h:40
static SearchRule::Ptr createInstance(const QByteArray &field=QByteArray(), Function function=FuncContains, const QString &contents=QString())
Creates a new search rule of a certain type by instantiating the appropriate subclass depending on th...
QString path(const QString &relativePath)
The filter dialog.
bool isEmpty() const const
QString toString(QStringView format, QCalendar cal) const const
const_reference at(qsizetype i) const const
qsizetype count() const const
bool isEmpty() const const
qsizetype length() const const
QString number(double n, char format, int precision)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
QString trimmed() const const
QString join(QChar separator) const const
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
bool atEnd() const const
QString readLine(qint64 maxlen)
QUrl fromLocalFile(const QString &localFile)
bool isValid() const const
QString path(ComponentFormattingOptions options) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:00 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.