1 /* filter_list_model.cpp
2  * Model for all filter types
3  *
4  * Wireshark - Network traffic analyzer
5  * By Gerald Combs <gerald@wireshark.org>
6  * Copyright 1998 Gerald Combs
7  *
8  * SPDX-License-Identifier: GPL-2.0-or-later
9  */
10 
11 #include <glib.h>
12 
13 #include <wsutil/filesystem.h>
14 
15 #include <ui/qt/utils/qt_ui_utils.h>
16 #include <ui/qt/utils/wireshark_mime_data.h>
17 #include <ui/qt/models/filter_list_model.h>
18 #include <ui/qt/models/profile_model.h>
19 
20 #include <QFile>
21 #include <QTextStream>
22 #include <QRegularExpression>
23 #include <QDir>
24 #include <QMimeData>
25 
26 /*
27  * Old filter file name.
28  */
29 #define FILTER_FILE_NAME      "filters"
30 
31 /*
32  * Capture filter file name.
33  */
34 #define CFILTER_FILE_NAME     "cfilters"
35 
36 /*
37  * Display filter file name.
38  */
39 #define DFILTER_FILE_NAME     "dfilters"
40 
FilterListModel(QObject * parent)41 FilterListModel::FilterListModel(QObject * parent) :
42     QAbstractListModel(parent),
43     type_(FilterListModel::Display)
44 {
45     reload();
46 }
47 
FilterListModel(FilterListModel::FilterListType type,QObject * parent)48 FilterListModel::FilterListModel(FilterListModel::FilterListType type, QObject * parent) :
49     QAbstractListModel(parent),
50     type_(type)
51 {
52     reload();
53 }
54 
reload()55 void FilterListModel::reload()
56 {
57     storage.clear();
58 
59     const char * cfile = (type_ == FilterListModel::Capture) ? CFILTER_FILE_NAME : DFILTER_FILE_NAME;
60 
61     /* Try personal config file first */
62     QString fileName = gchar_free_to_qstring(get_persconffile_path(cfile, TRUE));
63     if (fileName.length() <= 0 || ! QFileInfo::exists(fileName))
64         fileName = gchar_free_to_qstring(get_persconffile_path(FILTER_FILE_NAME, TRUE));
65     if (fileName.length() <= 0 || ! QFileInfo::exists(fileName))
66         fileName = gchar_free_to_qstring(get_datafile_path(cfile));
67     if (fileName.length() <= 0 || ! QFileInfo::exists(fileName))
68         return;
69 
70     QFile file(fileName);
71     /* Still can use the model, just have to start from an empty set */
72     if (! file.open(QIODevice::ReadOnly | QIODevice::Text))
73         return;
74 
75     QTextStream in(&file);
76     QRegularExpression rx("\\s*\\\"(.*?)\\\"\\s(.*)");
77     while (!in.atEnd())
78     {
79         QString line = in.readLine().trimmed();
80         if (line.startsWith("#") || line.indexOf("\"") <= -1)
81             continue;
82 
83         QRegularExpressionMatch match = rx.match(line);
84         if (match.hasMatch()) {
85             addFilter(match.captured(1), match.captured(2));
86         }
87     }
88 }
89 
setFilterType(FilterListModel::FilterListType type)90 void FilterListModel::setFilterType(FilterListModel::FilterListType type)
91 {
92     type_ = type;
93     reload();
94 }
95 
filterType() const96 FilterListModel::FilterListType FilterListModel::filterType() const
97 {
98     return type_;
99 }
100 
rowCount(const QModelIndex &) const101 int FilterListModel::rowCount(const QModelIndex &/* parent */) const
102 {
103     return storage.count();
104 }
105 
columnCount(const QModelIndex &) const106 int FilterListModel::columnCount(const QModelIndex &/* parent */) const
107 {
108     return 2;
109 }
110 
headerData(int section,Qt::Orientation orientation,int role) const111 QVariant FilterListModel::headerData(int section, Qt::Orientation orientation, int role) const
112 {
113     if (section >= columnCount() || section < 0 || orientation != Qt::Horizontal)
114         return QVariant();
115 
116     if (role == Qt::DisplayRole)
117     {
118         switch (section) {
119             case ColumnName:
120                 return tr("Filter Name");
121                 break;
122             case ColumnExpression:
123                 return tr("Filter Expression");
124                 break;
125         }
126     }
127 
128     return QVariant();
129 }
130 
data(const QModelIndex & index,int role) const131 QVariant FilterListModel::data(const QModelIndex &index, int role) const
132 {
133     if (! index.isValid() || index.row() >= rowCount())
134         return QVariant();
135 
136     QStringList row = storage.at(index.row()).split("\n");
137     if (role == Qt::DisplayRole)
138         return row.at(index.column());
139 
140     return QVariant();
141 }
142 
setData(const QModelIndex & index,const QVariant & value,int role)143 bool FilterListModel::setData(const QModelIndex &index, const QVariant &value, int role)
144 {
145     if (! index.isValid() || index.row() >= rowCount() || role != Qt::EditRole)
146         return false;
147 
148     QStringList row = storage.at(index.row()).split("\n");
149     if (row.count() <= index.column())
150         return false;
151 
152     if (index.column() == FilterListModel::ColumnName && value.toString().contains("\""))
153         return false;
154 
155     row[index.column()] = value.toString();
156     storage[index.row()] = row.join("\n");
157 
158     return true;
159 }
160 
flags(const QModelIndex & index) const161 Qt::ItemFlags FilterListModel::flags(const QModelIndex &index) const
162 {
163     Qt::ItemFlags fl = QAbstractListModel::flags(index);
164     fl |= Qt::ItemIsDropEnabled;
165 
166     if (! index.isValid() || index.row() >= rowCount())
167         return fl;
168 
169     fl |= Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
170 
171     return fl;
172 }
addFilter(QString name,QString expression)173 QModelIndex FilterListModel::addFilter(QString name, QString expression)
174 {
175     if (name.length() == 0 || expression.length() == 0)
176         return QModelIndex();
177 
178     beginInsertRows(QModelIndex(), rowCount(), rowCount());
179     storage << QString("%1\n%2").arg(name).arg(expression);
180     endInsertRows();
181 
182     return index(rowCount() - 1, 0);
183 }
184 
findByName(QString name)185 QModelIndex FilterListModel::findByName(QString name)
186 {
187     if (name.length() == 0)
188         return QModelIndex();
189 
190     for (int cnt = 0; cnt < rowCount(); cnt++)
191     {
192         if (storage.at(cnt).startsWith(QString("%1\n").arg(name)))
193             return index(cnt, 0);
194     }
195 
196     return QModelIndex();
197 }
198 
findByExpression(QString expression)199 QModelIndex FilterListModel::findByExpression(QString expression)
200 {
201     if (expression.length() == 0)
202         return QModelIndex();
203 
204     for (int cnt = 0; cnt < rowCount(); cnt++)
205     {
206         if (storage.at(cnt).endsWith(QString("\n%1").arg(expression)))
207             return index(cnt, 0);
208     }
209 
210     return QModelIndex();
211 }
212 
removeFilter(QModelIndex idx)213 void FilterListModel::removeFilter(QModelIndex idx)
214 {
215     if (! idx.isValid() || idx.row() >= rowCount())
216         return;
217 
218     beginRemoveRows(QModelIndex(), idx.row(), idx.row());
219     storage.removeAt(idx.row());
220     endRemoveRows();
221 }
222 
saveList()223 void FilterListModel::saveList()
224 {
225     QString filename = (type_ == FilterListModel::Capture) ? CFILTER_FILE_NAME : DFILTER_FILE_NAME;
226 
227     filename = QString("%1%2%3").arg(ProfileModel::activeProfilePath()).arg("/").arg(filename);
228     QFile file(filename);
229 
230     if (! file.open(QIODevice::WriteOnly | QIODevice::Text))
231         return;
232 
233     QTextStream out(&file);
234     for (int row = 0; row < rowCount(); row++)
235     {
236         QString line = QString("\"%1\"").arg(index(row, ColumnName).data().toString());
237         line.append(QString(" %1").arg(index(row, ColumnExpression).data().toString()));
238 
239 #ifdef _WIN32
240         line = line.append("\r\n");
241 #else
242         line = line.append("\n");
243 #endif
244         out << line;
245     }
246 
247     file.close();
248 }
249 
supportedDropActions() const250 Qt::DropActions FilterListModel::supportedDropActions() const
251 {
252     return Qt::MoveAction;
253 }
254 
mimeTypes() const255 QStringList FilterListModel::mimeTypes() const
256 {
257     return QStringList() << WiresharkMimeData::FilterListMimeType;
258 }
259 
mimeData(const QModelIndexList & indexes) const260 QMimeData *FilterListModel::mimeData(const QModelIndexList &indexes) const
261 {
262     QMimeData *mimeData = new QMimeData();
263     QStringList rows;
264 
265     foreach (const QModelIndex &index, indexes)
266     {
267         if (! rows.contains(QString::number(index.row())))
268             rows << QString::number(index.row());
269     }
270 
271     mimeData->setData(WiresharkMimeData::FilterListMimeType, rows.join(",").toUtf8());
272     return mimeData;
273 }
274 
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int,const QModelIndex & parent)275 bool FilterListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int /* column */, const QModelIndex & parent)
276 {
277     if (action != Qt::MoveAction)
278         return true;
279 
280     if (! data->hasFormat(WiresharkMimeData::FilterListMimeType))
281         return true;
282 
283     QStringList rows = QString(data->data(WiresharkMimeData::FilterListMimeType)).split(",");
284 
285     int insertRow = parent.isValid() ? parent.row() : row;
286 
287     /* for now, only single rows can be selected */
288     if (rows.count() > 0)
289     {
290         bool ok = false;
291         int strow = rows[0].toInt(&ok);
292         if (ok)
293         {
294             int storeTo = insertRow;
295             if (storeTo < 0 || storeTo >= storage.count())
296                 storeTo = storage.count() - 1;
297 
298             beginResetModel();
299             storage.move(strow, storeTo);
300             endResetModel();
301         }
302     }
303 
304     return true;
305 }
306