1 /* interface_tree_cache_model.cpp
2  * Model caching interface changes before sending them to global storage
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 
12 #include <ui/qt/models/interface_tree_cache_model.h>
13 
14 #include "glib.h"
15 
16 #include "epan/prefs.h"
17 
18 #include <ui/qt/utils/qt_ui_utils.h>
19 #include "ui/capture_globals.h"
20 #include "wsutil/utf8_entities.h"
21 
22 #include "wiretap/wtap.h"
23 
24 #include "wireshark_application.h"
25 
26 #include <QIdentityProxyModel>
27 
InterfaceTreeCacheModel(QObject * parent)28 InterfaceTreeCacheModel::InterfaceTreeCacheModel(QObject *parent) :
29     QIdentityProxyModel(parent)
30 {
31     /* ATTENTION: This cache model is not intended to be used with anything
32      * else then InterfaceTreeModel, and will break with anything else
33      * leading to unintended results. */
34     sourceModel = new InterfaceTreeModel(parent);
35 
36     QIdentityProxyModel::setSourceModel(sourceModel);
37     storage = new QMap<int, QMap<InterfaceTreeColumns, QVariant> *>();
38 
39     checkableColumns << IFTREE_COL_HIDDEN << IFTREE_COL_PROMISCUOUSMODE;
40 #ifdef HAVE_PCAP_CREATE
41     checkableColumns << IFTREE_COL_MONITOR_MODE;
42 #endif
43 
44     editableColumns << IFTREE_COL_COMMENT << IFTREE_COL_SNAPLEN << IFTREE_COL_PIPE_PATH;
45 
46 #ifdef CAN_SET_CAPTURE_BUFFER_SIZE
47     editableColumns << IFTREE_COL_BUFFERLEN;
48 #endif
49 }
50 
~InterfaceTreeCacheModel()51 InterfaceTreeCacheModel::~InterfaceTreeCacheModel()
52 {
53 #ifdef HAVE_LIBPCAP
54     /* This list should only exist, if the dialog is closed, without calling save first */
55     newDevices.clear();
56 #endif
57 
58     delete storage;
59     delete sourceModel;
60 }
61 
getColumnContent(int idx,int col,int role)62 QVariant InterfaceTreeCacheModel::getColumnContent(int idx, int col, int role)
63 {
64     return InterfaceTreeCacheModel::data(index(idx, col), role);
65 }
66 
67 #ifdef HAVE_LIBPCAP
reset(int row)68 void InterfaceTreeCacheModel::reset(int row)
69 {
70     if (row < 0)
71     {
72         delete storage;
73         storage = new QMap<int, QMap<InterfaceTreeColumns, QVariant> *>();
74     }
75     else
76     {
77         if (storage->count() > row)
78             storage->remove(storage->keys().at(row));
79     }
80 }
81 
saveNewDevices()82 void InterfaceTreeCacheModel::saveNewDevices()
83 {
84     QList<interface_t>::const_iterator it = newDevices.constBegin();
85     /* idx is used for iterating only over the indices of the new devices. As all new
86      * devices are stored with an index higher then sourceModel->rowCount(), we start
87      * only with those storage indices.
88      * it is just the iterator over the new devices. A new device must not necessarily
89      * have storage, which will lead to that device not being stored in global_capture_opts */
90     for (int idx = sourceModel->rowCount(); it != newDevices.constEnd(); ++it, idx++)
91     {
92         interface_t *device = const_cast<interface_t *>(&(*it));
93         bool useDevice = false;
94 
95         QMap<InterfaceTreeColumns, QVariant> * dataField = storage->value(idx, 0);
96         /* When devices are being added, they are added using generic values. So only devices
97          * whose data have been changed should be used from here on out. */
98         if (dataField != 0)
99         {
100             if (device->if_info.type != IF_PIPE)
101             {
102                 continue;
103             }
104 
105             if (device->if_info.type == IF_PIPE)
106             {
107                 QVariant saveValue = dataField->value(IFTREE_COL_PIPE_PATH);
108                 if (saveValue.isValid())
109                 {
110                     g_free(device->if_info.name);
111                     device->if_info.name = qstring_strdup(saveValue.toString());
112 
113                     g_free(device->name);
114                     device->name = qstring_strdup(saveValue.toString());
115 
116                     g_free(device->display_name);
117                     device->display_name = qstring_strdup(saveValue.toString());
118                     useDevice = true;
119                 }
120             }
121 
122             if (useDevice)
123                 g_array_append_val(global_capture_opts.all_ifaces, *device);
124 
125         }
126 
127         /* All entries of this new devices have been considered */
128         storage->remove(idx);
129         delete dataField;
130     }
131 
132     newDevices.clear();
133 }
134 
save()135 void InterfaceTreeCacheModel::save()
136 {
137     if (storage->count() == 0)
138         return;
139 
140     QMap<char**, QStringList> prefStorage;
141 
142     /* No devices are hidden until checking "Show" state */
143     prefStorage[&prefs.capture_devices_hide] = QStringList();
144 
145     /* Storing new devices first including their changed values */
146     saveNewDevices();
147 
148 
149     for (unsigned int idx = 0; idx < global_capture_opts.all_ifaces->len; idx++)
150     {
151         interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
152 
153         if (! device->name)
154             continue;
155 
156         /* Try to load a saved value row for this index */
157         QMap<InterfaceTreeColumns, QVariant> * dataField = storage->value(idx, 0);
158 
159         /* Handle the storing of values for this device here */
160         if (dataField)
161         {
162             QMap<InterfaceTreeColumns, QVariant>::const_iterator it = dataField->constBegin();
163             while (it != dataField->constEnd())
164             {
165                 InterfaceTreeColumns col = it.key();
166                 QVariant saveValue = it.value();
167 
168                 /* Setting the field values for each individual saved value cannot be generic, as the
169                  * struct cannot be accessed in a generic way. Therefore below, each individually changed
170                  * value has to be handled separately. Comments are stored only in the preference file
171                  * and applied to the data name during loading. Therefore comments are not handled here */
172 
173                 if (col == IFTREE_COL_HIDDEN)
174                 {
175                     device->hidden = saveValue.toBool();
176                 }
177                 else if (device->if_info.type == IF_EXTCAP)
178                 {
179                     /* extcap interfaces do not have the following columns.
180                      * ATTENTION: all generic columns must be added, BEFORE this
181                      * if-clause, or they will be ignored for extcap interfaces */
182                 }
183                 else if (col == IFTREE_COL_PROMISCUOUSMODE)
184                 {
185                     device->pmode = saveValue.toBool();
186                 }
187 #ifdef HAVE_PCAP_CREATE
188                 else if (col == IFTREE_COL_MONITOR_MODE)
189                 {
190                     device->monitor_mode_enabled = saveValue.toBool();
191                 }
192 #endif
193                 else if (col == IFTREE_COL_SNAPLEN)
194                 {
195                     int iVal = saveValue.toInt();
196                     if (iVal != WTAP_MAX_PACKET_SIZE_STANDARD)
197                     {
198                         device->has_snaplen = true;
199                         device->snaplen = iVal;
200                     }
201                     else
202                     {
203                         device->has_snaplen = false;
204                         device->snaplen = WTAP_MAX_PACKET_SIZE_STANDARD;
205                     }
206                 }
207 #ifdef CAN_SET_CAPTURE_BUFFER_SIZE
208                 else if (col == IFTREE_COL_BUFFERLEN)
209                 {
210                     device->buffer = saveValue.toInt();
211                 }
212 #endif
213                 ++it;
214             }
215         }
216 
217         QVariant content = getColumnContent(idx, IFTREE_COL_HIDDEN, Qt::CheckStateRole);
218         if (content.isValid() && static_cast<Qt::CheckState>(content.toInt()) == Qt::Unchecked)
219             prefStorage[&prefs.capture_devices_hide] << QString(device->name);
220 
221         content = getColumnContent(idx, IFTREE_COL_COMMENT);
222         if (content.isValid() && content.toString().size() > 0)
223             prefStorage[&prefs.capture_devices_descr] << QString("%1(%2)").arg(device->name).arg(content.toString());
224 
225         bool allowExtendedColumns = true;
226 
227         if (device->if_info.type == IF_EXTCAP)
228             allowExtendedColumns = false;
229 
230         if (allowExtendedColumns)
231         {
232             content = getColumnContent(idx, IFTREE_COL_PROMISCUOUSMODE, Qt::CheckStateRole);
233             if (content.isValid())
234             {
235                 bool value = static_cast<Qt::CheckState>(content.toInt()) == Qt::Checked;
236                 prefStorage[&prefs.capture_devices_pmode]  << QString("%1(%2)").arg(device->name).arg(value ? 1 : 0);
237             }
238 
239 #ifdef HAVE_PCAP_CREATE
240             content = getColumnContent(idx, IFTREE_COL_MONITOR_MODE, Qt::CheckStateRole);
241             if (content.isValid() && static_cast<Qt::CheckState>(content.toInt()) == Qt::Checked)
242                     prefStorage[&prefs.capture_devices_monitor_mode] << QString(device->name);
243 #endif
244 
245             content = getColumnContent(idx, IFTREE_COL_SNAPLEN);
246             if (content.isValid())
247             {
248                 int value = content.toInt();
249                 prefStorage[&prefs.capture_devices_snaplen]  <<
250                         QString("%1:%2(%3)").arg(device->name).
251                         arg(device->has_snaplen ? 1 : 0).
252                         arg(device->has_snaplen ? value : WTAP_MAX_PACKET_SIZE_STANDARD);
253             }
254 
255 #ifdef CAN_SET_CAPTURE_BUFFER_SIZE
256             content = getColumnContent(idx, IFTREE_COL_BUFFERLEN);
257             if (content.isValid())
258             {
259                 int value = content.toInt();
260                 if (value != -1)
261                 {
262                     prefStorage[&prefs.capture_devices_buffersize]  <<
263                             QString("%1(%2)").arg(device->name).
264                             arg(value);
265                 }
266             }
267 #endif
268         }
269     }
270 
271     QMap<char**, QStringList>::const_iterator it = prefStorage.constBegin();
272     while (it != prefStorage.constEnd())
273     {
274         char ** key = it.key();
275 
276         g_free(*key);
277         *key = qstring_strdup(it.value().join(","));
278 
279         ++it;
280     }
281 
282     wsApp->emitAppSignal(WiresharkApplication::LocalInterfacesChanged);
283 }
284 #endif
285 
rowCount(const QModelIndex & parent) const286 int InterfaceTreeCacheModel::rowCount(const QModelIndex & parent) const
287 {
288     int totalCount = sourceModel->rowCount(parent);
289 #ifdef HAVE_LIBPCAP
290     totalCount += newDevices.size();
291 #endif
292     return totalCount;
293 }
294 
changeIsAllowed(InterfaceTreeColumns col) const295 bool InterfaceTreeCacheModel::changeIsAllowed(InterfaceTreeColumns col) const
296 {
297     if (editableColumns.contains(col) || checkableColumns.contains(col))
298         return true;
299     return false;
300 }
301 
302 #ifdef HAVE_LIBPCAP
lookup(const QModelIndex & index) const303 const interface_t * InterfaceTreeCacheModel::lookup(const QModelIndex &index) const
304 {
305     const interface_t * result = 0;
306 
307     if (! index.isValid() || ! global_capture_opts.all_ifaces)
308         return result;
309 
310     int idx = index.row();
311 
312     if ((unsigned int) idx >= global_capture_opts.all_ifaces->len)
313     {
314         idx = idx - global_capture_opts.all_ifaces->len;
315         if (idx < newDevices.size())
316             result = &newDevices[idx];
317     }
318     else
319     {
320         result = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
321     }
322 
323     return result;
324 }
325 #endif
326 
327 /* This checks if the column can be edited for the given index. This differs from
328  * isAvailableField in such a way, that it is only used in flags and not any
329  * other method.*/
isAllowedToBeEdited(const QModelIndex & index) const330 bool InterfaceTreeCacheModel::isAllowedToBeEdited(const QModelIndex &index) const
331 {
332 #ifndef HAVE_LIBPCAP
333     Q_UNUSED(index);
334 #else
335     const interface_t * device = lookup(index);
336     if (device == 0)
337         return false;
338 
339     InterfaceTreeColumns col = (InterfaceTreeColumns) index.column();
340     if (device->if_info.type == IF_EXTCAP)
341     {
342         /* extcap interfaces do not have those settings */
343         if (col == IFTREE_COL_PROMISCUOUSMODE || col == IFTREE_COL_SNAPLEN)
344             return false;
345 #ifdef CAN_SET_CAPTURE_BUFFER_SIZE
346         if (col == IFTREE_COL_BUFFERLEN)
347             return false;
348 #endif
349     }
350 #endif
351     return true;
352 }
353 
354 // Whether this field is available for modification and display.
isAvailableField(const QModelIndex & index) const355 bool InterfaceTreeCacheModel::isAvailableField(const QModelIndex &index) const
356 {
357 #ifndef HAVE_LIBPCAP
358     Q_UNUSED(index);
359 #else
360     const interface_t * device = lookup(index);
361 
362     if (device == 0)
363         return false;
364 
365     InterfaceTreeColumns col = (InterfaceTreeColumns) index.column();
366     if (col == IFTREE_COL_HIDDEN)
367     {
368         // Do not allow default capture interface to be hidden.
369         if (! g_strcmp0(prefs.capture_device, device->display_name))
370             return false;
371     }
372 #endif
373 
374     return true;
375 }
376 
flags(const QModelIndex & index) const377 Qt::ItemFlags InterfaceTreeCacheModel::flags(const QModelIndex &index) const
378 {
379     if (! index.isValid())
380         return Qt::ItemFlags();
381 
382     Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
383 
384     InterfaceTreeColumns col = (InterfaceTreeColumns) index.column();
385 
386     if (changeIsAllowed(col) && isAvailableField(index) && isAllowedToBeEdited(index))
387     {
388         if (checkableColumns.contains(col))
389         {
390             flags |= Qt::ItemIsUserCheckable;
391         }
392         else
393         {
394             flags |= Qt::ItemIsEditable;
395         }
396     }
397 
398     return flags;
399 }
400 
setData(const QModelIndex & index,const QVariant & value,int role)401 bool InterfaceTreeCacheModel::setData(const QModelIndex &index, const QVariant &value, int role)
402 {
403     if (! index.isValid())
404         return false;
405 
406     if (! isAvailableField(index))
407         return false;
408 
409     int row = index.row();
410     InterfaceTreeColumns col = (InterfaceTreeColumns)index.column();
411 
412     if (role == Qt::CheckStateRole || role == Qt::EditRole)
413     {
414         if (changeIsAllowed(col) )
415         {
416             QVariant saveValue = value;
417 
418             QMap<InterfaceTreeColumns, QVariant> * dataField = 0;
419             /* obtain the list of already stored changes for this row. If none exist
420              * create a new storage row for this entry */
421             if ((dataField = storage->value(row, 0)) == 0)
422             {
423                 dataField = new QMap<InterfaceTreeColumns, QVariant>();
424                 storage->insert(row, dataField);
425             }
426 
427             dataField->insert(col, saveValue);
428 
429             return true;
430         }
431     }
432 
433     return false;
434 }
435 
data(const QModelIndex & index,int role) const436 QVariant InterfaceTreeCacheModel::data(const QModelIndex &index, int role) const
437 {
438     if (! index.isValid())
439         return QVariant();
440 
441     int row = index.row();
442 
443     InterfaceTreeColumns col = (InterfaceTreeColumns)index.column();
444 
445     if (isAvailableField(index) && isAllowedToBeEdited(index))
446     {
447         if (((role == Qt::DisplayRole || role == Qt::EditRole) && editableColumns.contains(col)) ||
448                 (role == Qt::CheckStateRole && checkableColumns.contains(col)) )
449         {
450             QMap<InterfaceTreeColumns, QVariant> * dataField = 0;
451             if ((dataField = storage->value(row, 0)) != 0)
452             {
453                 if (dataField->contains(col))
454                 {
455                     return dataField->value(col, QVariant());
456                 }
457             }
458         }
459     }
460     else
461     {
462         if (role == Qt::CheckStateRole)
463             return QVariant();
464         else if (role == Qt::DisplayRole)
465             return QString(UTF8_EM_DASH);
466     }
467 
468     if (row < sourceModel->rowCount())
469     {
470         return sourceModel->data(index, role);
471     }
472 #ifdef HAVE_LIBPCAP
473     else
474     {
475         /* Handle all fields, which will have to be displayed for new devices. Only pipes
476          * are supported at the moment, so the information to be displayed is pretty limited.
477          * After saving, the devices are stored in global_capture_opts and no longer
478          * classify as new devices. */
479         const interface_t * device = lookup(index);
480 
481         if (device != 0)
482         {
483             if (role == Qt::DisplayRole || role == Qt::EditRole)
484             {
485                 if (col == IFTREE_COL_PIPE_PATH ||
486                         col == IFTREE_COL_NAME ||
487                         col == IFTREE_COL_DESCRIPTION)
488                 {
489 
490                     QMap<InterfaceTreeColumns, QVariant> * dataField = 0;
491                     if ((dataField = storage->value(row, 0)) != 0 &&
492                             dataField->contains(IFTREE_COL_PIPE_PATH))
493                     {
494                         return dataField->value(IFTREE_COL_PIPE_PATH, QVariant());
495                     }
496                     else
497                         return QString(device->name);
498                 }
499                 else if (col == IFTREE_COL_TYPE)
500                 {
501                     return QVariant::fromValue((int)device->if_info.type);
502                 }
503             }
504             else if (role == Qt::CheckStateRole)
505             {
506                 if (col == IFTREE_COL_HIDDEN)
507                 {
508                     // Do not allow default capture interface to be hidden.
509                     if (! g_strcmp0(prefs.capture_device, device->display_name))
510                         return QVariant();
511 
512                     /* Hidden is a de-selection, therefore inverted logic here */
513                     return device->hidden ? Qt::Unchecked : Qt::Checked;
514                 }
515             }
516         }
517     }
518 #endif
519 
520     return QVariant();
521 }
522 
523 #ifdef HAVE_LIBPCAP
index(int row,int column,const QModelIndex & parent) const524 QModelIndex InterfaceTreeCacheModel::index(int row, int column, const QModelIndex &parent) const
525 {
526     if (row >= sourceModel->rowCount() && (row - sourceModel->rowCount()) < newDevices.count())
527     {
528         return createIndex(row, column, (void *)0);
529     }
530 
531     return QIdentityProxyModel::index(row, column, parent);
532 }
533 
addDevice(const interface_t * newDevice)534 void InterfaceTreeCacheModel::addDevice(const interface_t * newDevice)
535 {
536     emit beginInsertRows(QModelIndex(), rowCount(), rowCount());
537     newDevices << *newDevice;
538     emit endInsertRows();
539 }
540 
deleteDevice(const QModelIndex & index)541 void InterfaceTreeCacheModel::deleteDevice(const QModelIndex &index)
542 {
543     if (! index.isValid())
544         return;
545 
546     emit beginRemoveRows(QModelIndex(), index.row(), index.row());
547 
548     int row = index.row();
549 
550     /* device is in newDevices */
551     if (row >= sourceModel->rowCount())
552     {
553         int newDeviceIdx = row - sourceModel->rowCount();
554 
555         newDevices.removeAt(newDeviceIdx);
556         if (storage->contains(index.row()))
557             storage->remove(index.row());
558 
559         /* The storage array has to be resorted, if the index, that was removed
560          * had been in the middle of the array. Can't start at index.row(), as
561          * it may not be contained in storage
562          * We must iterate using a list, not an iterator, otherwise the change
563          * will fold on itself. */
564         QList<int> storageKeys = storage->keys();
565         for (int i = 0; i < storageKeys.size(); ++i)
566         {
567             int key = storageKeys.at(i);
568             if (key > index.row())
569             {
570                 storage->insert(key - 1, storage->value(key));
571                 storage->remove(key);
572             }
573         }
574 
575         emit endRemoveRows();
576     }
577     else
578     {
579         interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, row);
580         capture_opts_free_interface_t(device);
581         global_capture_opts.all_ifaces = g_array_remove_index(global_capture_opts.all_ifaces, row);
582         emit endRemoveRows();
583         wsApp->emitAppSignal(WiresharkApplication::LocalInterfacesChanged);
584     }
585 }
586 #endif
587