Mailcommon

filterimporterthunderbird.cpp
1 /*
2  SPDX-FileCopyrightText: 2011-2022 Laurent Montel <[email protected]>
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 
17 using namespace MailCommon;
18 
19 FilterImporterThunderbird::FilterImporterThunderbird(QFile *file, bool interactive)
20  : FilterImporterAbstract(interactive)
21 {
22  QTextStream stream(file);
23  readStream(stream);
24 }
25 
26 FilterImporterThunderbird::FilterImporterThunderbird(QString string, bool interactive)
27  : FilterImporterAbstract(interactive)
28 {
29  QTextStream stream(&string);
30  readStream(stream);
31 }
32 
33 FilterImporterThunderbird::~FilterImporterThunderbird() = default;
34 
35 void 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 
47 QString FilterImporterThunderbird::defaultSeaMonkeyFiltersSettingsPath()
48 {
49  return MailImporter::FilterSeaMonkey::defaultSettingsPath();
50 }
51 
52 QString FilterImporterThunderbird::defaultIcedoveFiltersSettingsPath()
53 {
54  return MailImporter::FilterIcedove::defaultSettingsPath();
55 }
56 
57 QString FilterImporterThunderbird::defaultThunderbirdFiltersSettingsPath()
58 {
59  return MailImporter::FilterThunderbird::defaultSettingsPath();
60 }
61 
62 MailCommon::MailFilter *FilterImporterThunderbird::parseLine(QTextStream &stream, QString line, MailCommon::MailFilter *filter)
63 {
64  if (line.startsWith(QLatin1String("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(QLatin1String("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(QLatin1String("actionValue="))) {
77  value = cleanArgument(line, QStringLiteral("actionValue="));
78  // change priority
79  if (actionName == QLatin1String("Change priority")) {
80  QStringList lstValue;
81  lstValue << QStringLiteral("X-Priority");
82  if (value == QLatin1String("Highest")) {
83  value = QStringLiteral("1 (Highest)");
84  } else if (value == QLatin1String("High")) {
85  value = QStringLiteral("2 (High)");
86  } else if (value == QLatin1String("Normal")) {
87  value = QStringLiteral("3 (Normal)");
88  } else if (value == QLatin1String("Low")) {
89  value = QStringLiteral("4 (Low)");
90  } else if (value == QLatin1String("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 == QLatin1String("copy") || actionName == QLatin1String("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(QLatin1String("enabled="))) {
115  line = cleanArgument(line, QStringLiteral("enabled="));
116  if (line == QLatin1String("no")) {
117  filter->setEnabled(false);
118  }
119  } else if (line.startsWith(QLatin1String("condition="))) {
120  line = cleanArgument(line, QStringLiteral("condition="));
121  extractConditions(line, filter);
122  } else if (line.startsWith(QLatin1String("type="))) {
123  line = cleanArgument(line, QStringLiteral("type="));
124  extractType(line, filter);
125  } else if (line.startsWith(QLatin1String("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(QLatin1String("logging="))) {
131  line = cleanArgument(line, QStringLiteral("logging="));
132  if (line == QLatin1String("no")) {
133  // TODO
134  } else if (line == QLatin1String("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 
145 void FilterImporterThunderbird::extractConditions(const QString &line, MailCommon::MailFilter *filter)
146 {
147  if (line.startsWith(QLatin1String("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(QLatin1String("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(QLatin1String("ALL"))) {
166  filter->pattern()->setOp(SearchPattern::OpAll);
167  } else {
168  qCDebug(MAILCOMMON_LOG) << " missing extract condition" << line;
169  }
170 }
171 
172 bool 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 == QLatin1String("subject")) {
216  fieldName = "subject";
217  } else if (field == QLatin1String("from")) {
218  fieldName = "from";
219  } else if (field == QLatin1String("body")) {
220  fieldName = "<body>";
221  } else if (field == QLatin1String("date")) {
222  fieldName = "<date>";
223  } else if (field == QLatin1String("priority")) {
224  // TODO
225  } else if (field == QLatin1String("status")) {
226  fieldName = "<status>";
227  } else if (field == QLatin1String("to")) {
228  fieldName = "to";
229  } else if (field == QLatin1String("cc")) {
230  fieldName = "cc";
231  } else if (field == QLatin1String("to or cc")) {
232  fieldName = "<recipients>";
233  } else if (field == QLatin1String("all addresses")) {
234  fieldName = "<recipients>";
235  } else if (field == QLatin1String("age in days")) {
236  fieldName = "<age in days>";
237  } else if (field == QLatin1String("label")) {
238  // TODO
239  } else if (field == QLatin1String("tag")) {
240  fieldName = "<tag>";
241  } else if (field == QLatin1String("size")) {
242  fieldName = "<size>";
243  } else if (field == QLatin1String("from in ab")) {
244  // TODO
245  } else if (field == QLatin1String("junk status")) {
246  // TODO
247  } else if (field == QLatin1String("junk percent")) {
248  // TODO
249  } else if (field == QLatin1String("junk score origin")) {
250  // TODO
251  } else if (field == QLatin1String("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 == QLatin1String("contains")) {
281  functionName = SearchRule::FuncContains;
282  } else if (function == QLatin1String("doesn't contain")) {
283  functionName = SearchRule::FuncContainsNot;
284  } else if (function == QLatin1String("is")) {
285  functionName = SearchRule::FuncEquals;
286  } else if (function == QLatin1String("isn't")) {
287  functionName = SearchRule::FuncNotEqual;
288  } else if (function == QLatin1String("is empty")) {
289  // TODO
290  } else if (function == QLatin1String("isn't empty")) {
291  // TODO
292  } else if (function == QLatin1String("is before")) {
293  functionName = SearchRule::FuncIsLess;
294  } else if (function == QLatin1String("is after")) {
295  functionName = SearchRule::FuncIsGreater;
296  } else if (function == QLatin1String("is higher than")) {
297  functionName = SearchRule::FuncIsGreater;
298  } else if (function == QLatin1String("is lower than")) {
299  functionName = SearchRule::FuncIsLess;
300  } else if (function == QLatin1String("begins with")) {
301  functionName = SearchRule::FuncStartWith;
302  } else if (function == QLatin1String("ends with")) {
303  functionName = SearchRule::FuncEndWith;
304  } else if (function == QLatin1String("is in ab")) {
305  functionName = SearchRule::FuncIsInAddressbook;
306  } else if (function == QLatin1String("isn't in ab")) {
307  functionName = SearchRule::FuncIsNotInAddressbook;
308  } else if (function == QLatin1String("is greater than")) {
309  functionName = SearchRule::FuncIsGreater;
310  } else if (function == QLatin1String("is less than")) {
311  functionName = SearchRule::FuncIsLess;
312  } else if (function == QLatin1String("matches")) {
313  functionName = SearchRule::FuncEquals;
314  } else if (function == QLatin1String("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 == QLatin1String("read")) {
324  contentsName = QStringLiteral("Read");
325  } else if (contents == QLatin1String("unread")) {
326  contentsName = QStringLiteral("Unread");
327  } else if (contents == QLatin1String("new")) {
328  contentsName = QStringLiteral("New");
329  } else if (contents == QLatin1String("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>") {
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 
353 QString 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 == QLatin1String("Move to folder")) {
379  actionName = QStringLiteral("transfer");
380  } else if (line == QLatin1String("Forward")) {
381  actionName = QStringLiteral("forward");
382  } else if (line == QLatin1String("Mark read")) {
383  actionName = QStringLiteral("set status");
384  value = QStringLiteral("R");
385  } else if (line == QLatin1String("Mark unread")) {
386  actionName = QStringLiteral("set status");
387  value = QStringLiteral("U"); // TODO verify
388  } else if (line == QLatin1String("Copy to folder")) {
389  actionName = QStringLiteral("copy");
390  } else if (line == QLatin1String("AddTag")) {
391  actionName = QStringLiteral("add tag");
392  } else if (line == QLatin1String("Delete")) {
393  actionName = QStringLiteral("delete");
394  } else if (line == QLatin1String("Change priority")) {
395  actionName = QStringLiteral("Change priority"); // Doesn't exist in kmail but we help us to importing
396  } else if (line == QLatin1String("Ignore thread")) {
397  } else if (line == QLatin1String("Ignore subthread")) {
398  } else if (line == QLatin1String("Watch thread")) {
399  } else if (line == QLatin1String("Mark flagged")) {
400  } else if (line == QLatin1String("Label")) {
401  } else if (line == QLatin1String("Reply")) {
402  actionName = QStringLiteral("set Reply-To");
403  } else if (line == QLatin1String("Stop execution")) {
404  filter->setStopProcessingHere(true);
405  return {};
406  } else if (line == QLatin1String("Delete from Pop3 server")) {
407  } else if (line == QLatin1String("JunkScore")) {
408  } else if (line == QLatin1String("Fetch body from Pop3Server")) {
409  } else if (line == QLatin1String("Custom")) {
410  }
411  if (actionName.isEmpty()) {
412  qCDebug(MAILCOMMON_LOG) << QStringLiteral(" missing convert method: %1").arg(line);
413  }
414  return actionName;
415 }
416 
417 void 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 
447 QString 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 }
std::shared_ptr< SearchRule > Ptr
Defines a pointer to a search rule.
Definition: searchrule.h:29
QString number(int n, int base)
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
int count(const T &value) const const
QString trimmed() const const
The MailFilter class.
Definition: mailfilter.h:28
bool isValid() const const
bool atEnd() const const
bool isEmpty() const const
QUrl fromLocalFile(const QString &localFile)
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...
Definition: searchrule.cpp:75
int length() const const
QString readLine(qint64 maxlen)
const T & at(int i) const const
QFuture< void > filter(Sequence &sequence, KeepFunctor filterFunction)
int toInt(bool *ok, int base) const const
QString join(const QString &separator) const const
Function
Describes operators for comparison of field and contents.
Definition: searchrule.h:40
QString & remove(int position, int n)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
LocaleWrapper locale()
bool isEmpty() const const
QString path(QUrl::ComponentFormattingOptions options) const const
QString toString(Qt::DateFormat format) const const
The filter dialog.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sat Oct 1 2022 04:00:53 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.