1 /* expert_info_dialog.cpp
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  */
9 
10 #include "expert_info_dialog.h"
11 #include <ui_expert_info_dialog.h>
12 
13 #include "file.h"
14 
15 #include <epan/epan_dissect.h>
16 #include <epan/expert.h>
17 #include <epan/stat_tap_ui.h>
18 #include <epan/tap.h>
19 
20 #include "wireshark_application.h"
21 
22 #include <QAction>
23 #include <QHash>
24 #include <QMenu>
25 #include <QMessageBox>
26 #include <QPushButton>
27 
28 // To do:
29 // - Test with custom expert levels (Preferences -> Expert).
30 // - Test with large captures.
31 // - Promote to a fourth pane in the main window?
32 // - Make colors configurable? In theory we could condense image/expert_indicators.svg,
33 //   down to one item, make sure it uses a single (or a few) base color(s), and generate
34 //   icons on the fly.
35 
ExpertInfoDialog(QWidget & parent,CaptureFile & capture_file)36 ExpertInfoDialog::ExpertInfoDialog(QWidget &parent, CaptureFile &capture_file) :
37     WiresharkDialog(parent, capture_file),
38     ui(new Ui::ExpertInfoDialog),
39     expert_info_model_(new ExpertInfoModel(capture_file)),
40     proxyModel_(new ExpertInfoProxyModel(this)),
41     display_filter_(QString())
42 {
43     ui->setupUi(this);
44 
45     proxyModel_->setSourceModel(expert_info_model_);
46     ui->expertInfoTreeView->setModel(proxyModel_);
47 
48     setWindowSubtitle(tr("Expert Information"));
49 
50     // Clicking on an item jumps to its associated packet. Make the dialog
51     // narrow so that we avoid obscuring the packet list.
52     int dlg_width = parent.width() * 3 / 5;
53     if (dlg_width < width()) dlg_width = width();
54     loadGeometry(dlg_width, parent.height());
55 
56     int one_em = fontMetrics().height();
57     ui->expertInfoTreeView->setColumnWidth(ExpertInfoProxyModel::colProxySummary, one_em * 25); // Arbitrary
58 
59     //Unfortunately this has to be done manually and not through .ui
60     ui->severitiesPushButton->setMenu(ui->menuShowExpert);
61 
62     ui->expertInfoTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
63     connect(ui->expertInfoTreeView, SIGNAL(customContextMenuRequested(QPoint)),
64                 SLOT(showExpertInfoMenu(QPoint)));
65 
66     QMenu *submenu;
67 
68     FilterAction::Action cur_action = FilterAction::ActionApply;
69     submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
70     foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
71         FilterAction *fa = new FilterAction(submenu, cur_action, at);
72         submenu->addAction(fa);
73         connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
74     }
75 
76     cur_action = FilterAction::ActionPrepare;
77     submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
78     foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
79         FilterAction *fa = new FilterAction(submenu, cur_action, at);
80         submenu->addAction(fa);
81         connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
82     }
83 
84     FilterAction *fa;
85     QList<FilterAction::Action> extra_actions =
86             QList<FilterAction::Action>() << FilterAction::ActionFind
87                                           << FilterAction::ActionColorize
88                                           << FilterAction::ActionWebLookup
89                                           << FilterAction::ActionCopy;
90 
91     foreach (FilterAction::Action extra_action, extra_actions) {
92         fa = new FilterAction(&ctx_menu_, extra_action);
93         ctx_menu_.addAction(fa);
94         connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
95     }
96 
97     //Add collapse/expand all menu options
98     QAction *collapse = new QAction(tr("Collapse All"), this);
99     ctx_menu_.addAction(collapse);
100     connect(collapse, SIGNAL(triggered()), this, SLOT(collapseTree()));
101 
102     QAction *expand = new QAction(tr("Expand All"), this);
103     ctx_menu_.addAction(expand);
104     connect(expand, SIGNAL(triggered()), this, SLOT(expandTree()));
105 
106     connect(&cap_file_, SIGNAL(captureEvent(CaptureEvent)),
107             this, SLOT(captureEvent(CaptureEvent)));
108     setDisplayFilter();
109     QTimer::singleShot(0, this, SLOT(retapPackets()));
110 }
111 
~ExpertInfoDialog()112 ExpertInfoDialog::~ExpertInfoDialog()
113 {
114     delete ui;
115     delete proxyModel_;
116     delete expert_info_model_;
117 }
118 
clearAllData()119 void ExpertInfoDialog::clearAllData()
120 {
121     expert_info_model_->clear();
122 }
123 
setDisplayFilter(const QString & display_filter)124 void ExpertInfoDialog::setDisplayFilter(const QString &display_filter)
125 {
126     display_filter_ = display_filter;
127     updateWidgets();
128 }
129 
getExpertInfoView()130 ExpertInfoTreeView* ExpertInfoDialog::getExpertInfoView()
131 {
132     return ui->expertInfoTreeView;
133 }
134 
retapPackets()135 void ExpertInfoDialog::retapPackets()
136 {
137     if (file_closed_) return;
138 
139     clearAllData();
140     removeTapListeners();
141 
142     if (!registerTapListener("expert",
143                              expert_info_model_,
144                              ui->limitCheckBox->isChecked() ? display_filter_.toUtf8().constData(): NULL,
145                              TL_REQUIRES_COLUMNS,
146                              ExpertInfoModel::tapReset,
147                              ExpertInfoModel::tapPacket,
148                              ExpertInfoModel::tapDraw)) {
149         return;
150     }
151 
152     cap_file_.retapPackets();
153 }
154 
captureEvent(CaptureEvent e)155 void ExpertInfoDialog::captureEvent(CaptureEvent e)
156 {
157     if (e.captureContext() == CaptureEvent::Retap)
158     {
159         switch (e.eventType())
160         {
161         case CaptureEvent::Started:
162             ui->limitCheckBox->setEnabled(false);
163             ui->groupBySummaryCheckBox->setEnabled(false);
164             break;
165         case CaptureEvent::Finished:
166             updateWidgets();
167             break;
168         default:
169             break;
170         }
171     }
172 }
173 
updateWidgets()174 void ExpertInfoDialog::updateWidgets()
175 {
176     ui->limitCheckBox->setEnabled(! file_closed_ && ! display_filter_.isEmpty());
177 
178     ui->actionShowError->setEnabled(expert_info_model_->numEvents(ExpertInfoModel::severityError) > 0);
179     ui->actionShowWarning->setEnabled(expert_info_model_->numEvents(ExpertInfoModel::severityWarn) > 0);
180     ui->actionShowNote->setEnabled(expert_info_model_->numEvents(ExpertInfoModel::severityNote) > 0);
181     ui->actionShowChat->setEnabled(expert_info_model_->numEvents(ExpertInfoModel::severityChat) > 0);
182     ui->actionShowComment->setEnabled(expert_info_model_->numEvents(ExpertInfoModel::severityComment) > 0);
183 
184     QString tooltip;
185     QString hint;
186 
187     if (file_closed_) {
188         tooltip = tr("Capture file closed.");
189         hint = tr("Capture file closed.");
190     } else if (display_filter_.isEmpty()) {
191          tooltip = tr("No display filter");
192          hint = tr("No display filter set.");
193     } else {
194         tooltip = tr("Limit information to \"%1\".").arg(display_filter_);
195         hint = tr("Display filter: \"%1\"").arg(display_filter_);
196     }
197 
198     ui->limitCheckBox->setToolTip(tooltip);
199     hint.prepend("<small><i>");
200     hint.append("</i></small>");
201     ui->hintLabel->setText(hint);
202 
203     ui->groupBySummaryCheckBox->setEnabled(!file_closed_);
204 }
205 
on_actionShowError_toggled(bool checked)206 void ExpertInfoDialog::on_actionShowError_toggled(bool checked)
207 {
208     proxyModel_->setSeverityFilter(PI_ERROR, !checked);
209     updateWidgets();
210 }
211 
on_actionShowWarning_toggled(bool checked)212 void ExpertInfoDialog::on_actionShowWarning_toggled(bool checked)
213 {
214     proxyModel_->setSeverityFilter(PI_WARN, !checked);
215     updateWidgets();
216 }
217 
on_actionShowNote_toggled(bool checked)218 void ExpertInfoDialog::on_actionShowNote_toggled(bool checked)
219 {
220     proxyModel_->setSeverityFilter(PI_NOTE, !checked);
221     updateWidgets();
222 }
223 
on_actionShowChat_toggled(bool checked)224 void ExpertInfoDialog::on_actionShowChat_toggled(bool checked)
225 {
226     proxyModel_->setSeverityFilter(PI_CHAT, !checked);
227     updateWidgets();
228 }
229 
on_actionShowComment_toggled(bool checked)230 void ExpertInfoDialog::on_actionShowComment_toggled(bool checked)
231 {
232     proxyModel_->setSeverityFilter(PI_COMMENT, !checked);
233     updateWidgets();
234 }
235 
236 
showExpertInfoMenu(QPoint pos)237 void ExpertInfoDialog::showExpertInfoMenu(QPoint pos)
238 {
239     bool enable = true;
240     QModelIndex expertIndex = ui->expertInfoTreeView->indexAt(pos);
241     if (!expertIndex.isValid()) {
242         return;
243     }
244 
245     if (proxyModel_->data(expertIndex.sibling(expertIndex.row(), ExpertInfoModel::colHf), Qt::DisplayRole).toInt() < 0) {
246         enable = false;
247     }
248 
249     foreach (QMenu *submenu, ctx_menu_.findChildren<QMenu*>()) {
250         submenu->setEnabled(enable && !file_closed_);
251     }
252     foreach (QAction *action, ctx_menu_.actions()) {
253         FilterAction *fa = qobject_cast<FilterAction *>(action);
254         bool action_enable = enable && !file_closed_;
255         if (fa && (fa->action() == FilterAction::ActionWebLookup || fa->action() == FilterAction::ActionCopy)) {
256             action_enable = enable;
257         }
258         action->setEnabled(action_enable);
259     }
260 
261     ctx_menu_.popup(ui->expertInfoTreeView->viewport()->mapToGlobal(pos));
262 }
263 
filterActionTriggered()264 void ExpertInfoDialog::filterActionTriggered()
265 {
266     QModelIndex modelIndex = ui->expertInfoTreeView->currentIndex();
267     FilterAction *fa = qobject_cast<FilterAction *>(QObject::sender());
268 
269     if (!fa || !modelIndex.isValid()) {
270         return;
271     }
272 
273     int hf_index = proxyModel_->data(modelIndex.sibling(modelIndex.row(), ExpertInfoModel::colHf), Qt::DisplayRole).toInt();
274 
275     if (hf_index > -1) {
276         QString filter_string;
277         if (fa->action() == FilterAction::ActionWebLookup) {
278             filter_string = QString("%1 %2")
279                     .arg(proxyModel_->data(modelIndex.sibling(modelIndex.row(), ExpertInfoModel::colProtocol), Qt::DisplayRole).toString())
280                     .arg(proxyModel_->data(modelIndex.sibling(modelIndex.row(), ExpertInfoModel::colSummary), Qt::DisplayRole).toString());
281         } else if (fa->action() == FilterAction::ActionCopy) {
282             filter_string = QString("%1 %2: %3")
283                     .arg(proxyModel_->data(modelIndex.sibling(modelIndex.row(), ExpertInfoModel::colPacket), Qt::DisplayRole).toUInt())
284                     .arg(proxyModel_->data(modelIndex.sibling(modelIndex.row(), ExpertInfoModel::colProtocol), Qt::DisplayRole).toString())
285                     .arg(proxyModel_->data(modelIndex.sibling(modelIndex.row(), ExpertInfoModel::colSummary), Qt::DisplayRole).toString());
286         } else {
287             filter_string = proto_registrar_get_abbrev(hf_index);
288         }
289 
290         if (! filter_string.isEmpty()) {
291             emit filterAction(filter_string, fa->action(), fa->actionType());
292         }
293     }
294 }
295 
collapseTree()296 void ExpertInfoDialog::collapseTree()
297 {
298     ui->expertInfoTreeView->collapseAll();
299 }
300 
expandTree()301 void ExpertInfoDialog::expandTree()
302 {
303     ui->expertInfoTreeView->expandAll();
304 }
305 
on_limitCheckBox_toggled(bool)306 void ExpertInfoDialog::on_limitCheckBox_toggled(bool)
307 {
308     retapPackets();
309 }
310 
on_groupBySummaryCheckBox_toggled(bool)311 void ExpertInfoDialog::on_groupBySummaryCheckBox_toggled(bool)
312 {
313     expert_info_model_->setGroupBySummary(ui->groupBySummaryCheckBox->isChecked());
314 }
315 
316 // Show child (packet list) items that match the contents of searchLineEdit.
on_searchLineEdit_textChanged(const QString & search_re)317 void ExpertInfoDialog::on_searchLineEdit_textChanged(const QString &search_re)
318 {
319     proxyModel_->setSummaryFilter(search_re);
320 }
321 
on_buttonBox_helpRequested()322 void ExpertInfoDialog::on_buttonBox_helpRequested()
323 {
324     wsApp->helpTopicAction(HELP_EXPERT_INFO_DIALOG);
325 }
326 
327 // Stat command + args
328 
329 static void
expert_info_init(const char *,void *)330 expert_info_init(const char *, void*) {
331     wsApp->emitStatCommandSignal("ExpertInfo", NULL, NULL);
332 }
333 
334 static stat_tap_ui expert_info_stat_ui = {
335     REGISTER_STAT_GROUP_GENERIC,
336     NULL,
337     "expert",
338     expert_info_init,
339     0,
340     NULL
341 };
342 
343 extern "C" {
344 
345 void register_tap_listener_qt_expert_info(void);
346 
347 void
register_tap_listener_qt_expert_info(void)348 register_tap_listener_qt_expert_info(void)
349 {
350     register_stat_tap_ui(&expert_info_stat_ui, NULL);
351 }
352 
353 }
354