1 /* decode_as_delegate.cpp
2  * Delegates for editing various field types in a Decode As record.
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 "decode_as_delegate.h"
12 
13 #include "epan/decode_as.h"
14 #include "epan/epan_dissect.h"
15 
16 #include <ui/qt/utils/variant_pointer.h>
17 
18 #include <QComboBox>
19 #include <QEvent>
20 #include <QLineEdit>
21 #include <QTreeView>
22 
23 typedef struct _dissector_info_t {
24     QString             proto_name;
25     dissector_handle_t  dissector_handle;
26 } dissector_info_t;
27 
Q_DECLARE_METATYPE(dissector_info_t *)28 Q_DECLARE_METATYPE(dissector_info_t *)
29 
30 DecodeAsDelegate::DecodeAsDelegate(QObject *parent, capture_file *cf)
31  : QStyledItemDelegate(parent),
32     cap_file_(cf)
33 {
34     cachePacketProtocols();
35 }
36 
indexToField(const QModelIndex & index) const37 DecodeAsItem* DecodeAsDelegate::indexToField(const QModelIndex &index) const
38 {
39     const QVariant v = index.model()->data(index, Qt::UserRole);
40     return static_cast<DecodeAsItem*>(v.value<void *>());
41 }
42 
cachePacketProtocols()43 void DecodeAsDelegate::cachePacketProtocols()
44 {
45     //cache the list of potential decode as protocols in the current packet
46     if (cap_file_ && cap_file_->edt) {
47 
48         wmem_list_frame_t * protos = wmem_list_head(cap_file_->edt->pi.layers);
49         guint8 curr_layer_num = 1;
50 
51         while (protos != NULL) {
52             int proto_id = GPOINTER_TO_INT(wmem_list_frame_data(protos));
53             const gchar * proto_name = proto_get_protocol_filter_name(proto_id);
54             for (GList *cur = decode_as_list; cur; cur = cur->next) {
55                 decode_as_t *entry = (decode_as_t *) cur->data;
56                 if (g_strcmp0(proto_name, entry->name) == 0) {
57                     packet_proto_data_t proto_data;
58 
59                     proto_data.table_ui_name = get_dissector_table_ui_name(entry->table_name);
60                     proto_data.proto_name = proto_name;
61                     proto_data.curr_layer_num = curr_layer_num;
62 
63                     packet_proto_list_.append(proto_data);
64                 }
65             }
66             protos = wmem_list_frame_next(protos);
67             curr_layer_num++;
68         }
69     }
70 }
71 
collectDAProtocols(QSet<QString> & all_protocols,QList<QString> & current_list) const72 void DecodeAsDelegate::collectDAProtocols(QSet<QString>& all_protocols, QList<QString>& current_list) const
73 {
74     // If a packet is selected group its tables at the top in order
75     // from last-dissected to first.
76 
77     //gather the initial list
78     for (GList *cur = decode_as_list; cur; cur = cur->next) {
79         decode_as_t *entry = (decode_as_t *) cur->data;
80         const char *table_name = get_dissector_table_ui_name(entry->table_name);
81         if (table_name) {
82             all_protocols.insert(get_dissector_table_ui_name(entry->table_name));
83         }
84     }
85 
86     //filter out those in selected packet
87     foreach(packet_proto_data_t proto, packet_proto_list_)
88     {
89         current_list.append(proto.table_ui_name);
90         all_protocols.remove(proto.table_ui_name);
91     }
92 }
93 
94 //Determine if there are multiple values in the selector field that would
95 //correspond to using a combo box
isSelectorCombo(DecodeAsItem * item) const96 bool DecodeAsDelegate::isSelectorCombo(DecodeAsItem* item) const
97 {
98     const gchar *proto_name = NULL;
99 
100     foreach(packet_proto_data_t proto, packet_proto_list_)
101     {
102         if (g_strcmp0(proto.table_ui_name, item->tableUIName_) == 0) {
103             proto_name = proto.proto_name;
104             break;
105         }
106     }
107 
108     for (GList *cur = decode_as_list; cur; cur = cur->next) {
109         decode_as_t *entry = (decode_as_t *) cur->data;
110         if ((g_strcmp0(proto_name, entry->name) == 0) &&
111             (g_strcmp0(item->tableName_, entry->table_name) == 0) &&
112             (cap_file_ && cap_file_->edt) &&
113             (entry->num_items > 1)) {
114                 return true;
115         }
116     }
117 
118     return false;
119 }
120 
decodeAddProtocol(const gchar *,const gchar * proto_name,gpointer value,gpointer user_data)121 void DecodeAsDelegate::decodeAddProtocol(const gchar *, const gchar *proto_name, gpointer value, gpointer user_data)
122 {
123     QMap<QString, dissector_info_t*>* proto_list = (QMap<QString, dissector_info_t*>*)user_data;
124 
125     if (!proto_list)
126         return;
127 
128     dissector_info_t  *dissector_info = new dissector_info_t();
129     dissector_info->proto_name = proto_name;
130     dissector_info->dissector_handle = (dissector_handle_t) value;
131 
132     proto_list->insert(proto_name, dissector_info);
133 }
134 
createEditor(QWidget * parentWidget,const QStyleOptionViewItem & option,const QModelIndex & index) const135 QWidget* DecodeAsDelegate::createEditor(QWidget *parentWidget, const QStyleOptionViewItem &option,
136                                   const QModelIndex &index) const
137 {
138     DecodeAsItem* item = indexToField(index);
139     QWidget *editor = nullptr;
140 
141     switch(index.column())
142     {
143     case DecodeAsModel::colTable:
144         {
145         QComboBox *cb_editor = new QComboBox(parentWidget);
146         QSet<QString> da_set;
147         QList<QString> packet_list;
148         QString table_ui_name;
149 
150         collectDAProtocols(da_set, packet_list);
151 
152         cb_editor->setSizeAdjustPolicy(QComboBox::AdjustToContents);
153 
154         //put the protocols from the packet first in the combo box
155         foreach (table_ui_name, packet_list) {
156             cb_editor->addItem(table_ui_name, table_ui_name);
157         }
158         if (packet_list.count() > 0) {
159             cb_editor->insertSeparator(packet_list.count());
160         }
161 
162         //put the rest of the protocols in the combo box
163         QList<QString> da_list = da_set.values();
164         std::sort(da_list.begin(), da_list.end());
165 
166         foreach (table_ui_name, da_list) {
167             cb_editor->addItem(table_ui_name, table_ui_name);
168         }
169 
170         //Make sure the combo box is at least as wide as the column
171         QTreeView* parentTree = (QTreeView*)parent();
172         int protoColWidth = parentTree->columnWidth(index.column());
173         if (protoColWidth > cb_editor->size().width())
174             cb_editor->setFixedWidth(protoColWidth);
175 
176         editor = cb_editor;
177         break;
178         }
179     case DecodeAsModel::colSelector:
180         {
181         QComboBox *cb_editor = NULL;
182         const gchar *proto_name = NULL;
183         bool edt_present = cap_file_ && cap_file_->edt;
184         gint8 curr_layer_num_saved = edt_present ? cap_file_->edt->pi.curr_layer_num : 0;
185 
186         foreach(packet_proto_data_t proto, packet_proto_list_)
187         {
188             if (g_strcmp0(proto.table_ui_name, item->tableUIName_) == 0) {
189                 if (edt_present) {
190                     cap_file_->edt->pi.curr_layer_num = proto.curr_layer_num;
191                 }
192                 proto_name = proto.proto_name;
193                 //XXX - break?  Or do we always want the last layer of tunnelled protocols?
194             }
195         }
196 
197         for (GList *cur = decode_as_list; cur; cur = cur->next) {
198             decode_as_t *entry = (decode_as_t *) cur->data;
199             if ((g_strcmp0(proto_name, entry->name) == 0) &&
200                 (g_strcmp0(item->tableName_, entry->table_name) == 0)) {
201                 if (edt_present) {
202                     if (entry->num_items > 1)
203                     {
204                         //only create a combobox if there is a choice of values, otherwise it looks funny
205                         cb_editor = new QComboBox(parentWidget);
206 
207                         //Don't limit user to just what's in combo box
208                         cb_editor->setEditable(true);
209 
210                         cb_editor->setSizeAdjustPolicy(QComboBox::AdjustToContents);
211 
212                         //add the current value of the column
213                         const QString& current_value = index.model()->data(index, Qt::EditRole).toString();
214                         if (!current_value.isEmpty())
215                             cb_editor->addItem(current_value);
216 
217                         //get the value(s) from the packet
218                         for (uint ni = 0; ni < entry->num_items; ni++) {
219                             if (entry->values[ni].num_values == 1) { // Skip over multi-value ("both") entries
220                                 QString entryStr = DecodeAsModel::entryString(entry->table_name,
221                                                                         entry->values[ni].build_values[0](&cap_file_->edt->pi));
222                                 //don't duplicate entries
223                                 if (cb_editor->findText(entryStr) < 0)
224                                     cb_editor->addItem(entryStr);
225                             }
226                         }
227                         cb_editor->setCurrentIndex(entry->default_index_value);
228 
229                         //Make sure the combo box is at least as wide as the column
230                         QTreeView* parentTree = (QTreeView*)parent();
231                         int protoColWidth = parentTree->columnWidth(index.column());
232                         if (protoColWidth > cb_editor->size().width())
233                             cb_editor->setFixedWidth(protoColWidth);
234 
235                     }
236                 }
237                 break;
238             }
239         }
240 
241         if (edt_present) {
242             cap_file_->edt->pi.curr_layer_num = curr_layer_num_saved;
243         }
244 
245         //if there isn't a need for a combobox, just let user have a text box for direct edit
246         if (cb_editor) {
247             editor = cb_editor;
248         } else {
249             editor = QStyledItemDelegate::createEditor(parentWidget, option, index);
250         }
251         break;
252         }
253 
254     case DecodeAsModel::colProtocol:
255         {
256         QComboBox *cb_editor = new QComboBox(parentWidget);
257         QMap<QString, dissector_info_t*> protocols;
258 
259         cb_editor->setSizeAdjustPolicy(QComboBox::AdjustToContents);
260 
261         for (GList *cur = decode_as_list; cur; cur = cur->next) {
262             decode_as_t *entry = (decode_as_t *) cur->data;
263             if (g_strcmp0(item->tableName_, entry->table_name) == 0) {
264                 entry->populate_list(entry->table_name, decodeAddProtocol, &protocols);
265                 break;
266             }
267         }
268 
269         cb_editor->addItem(DECODE_AS_NONE);
270         cb_editor->insertSeparator(cb_editor->count());
271 
272         //QMap already sorts the keys (protocols) alphabetically
273         QMap<QString, dissector_info_t*>::iterator protocol;
274         for (protocol = protocols.begin(); protocol != protocols.end(); ++protocol)
275         {
276             cb_editor->addItem(protocol.key(), VariantPointer<dissector_info_t>::asQVariant(protocol.value()));
277         }
278 
279         //Make sure the combo box is at least as wide as the column
280         QTreeView* parentTree = (QTreeView*)parent();
281         int protoColWidth = parentTree->columnWidth(index.column());
282         if (protoColWidth > cb_editor->size().width())
283             cb_editor->setFixedWidth(protoColWidth);
284 
285         editor = cb_editor;
286         break;
287         }
288     }
289 
290     if (editor) {
291         editor->setAutoFillBackground(true);
292     }
293     return editor;
294 }
295 
setEditorData(QWidget * editor,const QModelIndex & index) const296 void DecodeAsDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
297 {
298     DecodeAsItem* item = indexToField(index);
299 
300     switch(index.column())
301     {
302     case DecodeAsModel::colTable:
303     case DecodeAsModel::colProtocol:
304         {
305         QComboBox *combobox = static_cast<QComboBox *>(editor);
306         const QString &data = index.model()->data(index, Qt::EditRole).toString();
307         combobox->setCurrentText(data);
308         }
309         break;
310     case DecodeAsModel::colSelector:
311         if (isSelectorCombo(item)) {
312             QComboBox *combobox = static_cast<QComboBox *>(editor);
313             const QString &data = index.model()->data(index, Qt::EditRole).toString();
314             combobox->setCurrentText(data);
315         }
316         else {
317             QStyledItemDelegate::setEditorData(editor, index);
318         }
319         break;
320     default:
321         QStyledItemDelegate::setEditorData(editor, index);
322         break;
323     }
324 }
325 
setModelData(QWidget * editor,QAbstractItemModel * model,const QModelIndex & index) const326 void DecodeAsDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
327                               const QModelIndex &index) const
328 {
329     DecodeAsItem* item = indexToField(index);
330 
331     switch(index.column())
332     {
333     case DecodeAsModel::colTable:
334         {
335         QComboBox *combobox = static_cast<QComboBox *>(editor);
336         const QString &data = combobox->currentText();
337         model->setData(index, data, Qt::EditRole);
338         break;
339         }
340     case DecodeAsModel::colSelector:
341         if (isSelectorCombo(item)) {
342             QComboBox *combobox = static_cast<QComboBox *>(editor);
343             const QString &data = combobox->currentText();
344             model->setData(index, data, Qt::EditRole);
345         } else {
346             QStyledItemDelegate::setModelData(editor, model, index);
347         }
348         break;
349     case DecodeAsModel::colProtocol:
350         {
351         QComboBox *combobox = static_cast<QComboBox *>(editor);
352         const QString &data = combobox->currentText();
353         model->setData(index, data, Qt::EditRole);
354 
355         //set the dissector handle
356         QVariant var = combobox->itemData(combobox->currentIndex());
357         dissector_info_t* dissector_info = VariantPointer<dissector_info_t>::asPtr(var);
358         if (dissector_info != NULL) {
359             ((DecodeAsModel*)model)->setDissectorHandle(index, dissector_info->dissector_handle);
360         } else {
361             ((DecodeAsModel*)model)->setDissectorHandle(index, NULL);
362         }
363         break;
364         }
365     default:
366         QStyledItemDelegate::setModelData(editor, model, index);
367         break;
368     }
369 }
370 
371 #if 0
372 // Qt docs suggest overriding updateEditorGeometry, but the defaults seem sane.
373 void UatDelegate::updateEditorGeometry(QWidget *editor,
374         const QStyleOptionViewItem &option, const QModelIndex &index) const
375 {
376     QStyledItemDelegate::updateEditorGeometry(editor, option, index);
377 }
378 #endif
379