### Bug Summary

File: | builds/wireshark/wireshark/ui/qt/proto_tree.cpp
Warning: | line 425, column 9 Value stored to 'action' is never read

### Annotated Source Code

Press '?' to see keyboard shortcuts

Show analyzer invocation
clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name proto_tree.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -fno-delete-null-pointer-checks -mframe-pointer=all -relaxed-aliasing -fmath-errno -ffp-contract=on -fno-rounding-math -ffloat16-excess-precision=fast -fbfloat16-excess-precision=fast -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/builds/wireshark/wireshark/build -fcoverage-compilation-dir=/builds/wireshark/wireshark/build -resource-dir /usr/lib/llvm-21/lib/clang/21 -isystem /usr/include/glib-2.0 -isystem /usr/lib/x86_64-linux-gnu/glib-2.0/include -isystem /builds/wireshark/wireshark/build/ui/qt -isystem /builds/wireshark/wireshark/ui/qt -isystem /usr/include/x86_64-linux-gnu/qt6/QtWidgets -isystem /usr/include/x86_64-linux-gnu/qt6 -isystem /usr/include/x86_64-linux-gnu/qt6/QtCore -isystem /usr/lib/x86_64-linux-gnu/qt6/mkspecs/linux-g++ -isystem /usr/include/x86_64-linux-gnu/qt6/QtGui -isystem /usr/include/x86_64-linux-gnu/qt6/QtCore5Compat -isystem /usr/include/x86_64-linux-gnu/qt6/QtConcurrent -isystem /usr/include/x86_64-linux-gnu/qt6/QtPrintSupport -isystem /usr/include/x86_64-linux-gnu/qt6/QtMultimedia -isystem /usr/include/x86_64-linux-gnu/qt6/QtNetwork -isystem /usr/include/x86_64-linux-gnu/qt6/QtDBus -D G_DISABLE_DEPRECATED -D G_DISABLE_SINGLE_INCLUDES -D QT_CONCURRENT_LIB -D QT_CORE5COMPAT_LIB -D QT_CORE_LIB -D QT_DBUS_LIB -D QT_GUI_LIB -D QT_MULTIMEDIA_LIB -D QT_NETWORK_LIB -D QT_PRINTSUPPORT_LIB -D QT_WIDGETS_LIB -D WS_DEBUG -D WS_DEBUG_UTF_8 -I /builds/wireshark/wireshark/build/ui/qt/qtui_autogen/include -I /builds/wireshark/wireshark/build -I /builds/wireshark/wireshark -I /builds/wireshark/wireshark/include -D _GLIBCXX_ASSERTIONS -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/x86_64-linux-gnu/c++/14 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/backward -internal-isystem /usr/lib/llvm-21/lib/clang/21/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/14/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -fmacro-prefix-map=/builds/wireshark/wireshark/= -fmacro-prefix-map=/builds/wireshark/wireshark/build/= -fmacro-prefix-map=../= -Wno-format-nonliteral -std=c++17 -fdeprecated-macro -ferror-limit 19 -fwrapv -fwrapv-pointer -fstrict-flex-arrays=3 -stack-protector 2 -fstack-clash-protection -fcf-protection=full -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcxx-exceptions -fexceptions -fcolor-diagnostics -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /builds/wireshark/wireshark/sbout/2026-03-28-100342-3641-1 -x c++ /builds/wireshark/wireshark/ui/qt/proto_tree.cpp

Keyboard shortcuts:

- Use 'j/k' keys for keyboard navigation
- Use 'Shift+S' to show/hide relevant lines
- Use '?' to toggle this window

Close
Show only relevant lines Show control flow arrows

1 | /* proto_tree.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 <stdio.h>
11 |
12 | #include <ui/qt/proto_tree.h>
13 | #include <ui/qt/models/proto_tree_model.h>
14 |
15 | #include <epan/ftypes/ftypes.h>
16 | #include <epan/prefs.h>
17 | #include <epan/print.h>
18 | #include <epan/epan.h>
19 | #include <epan/epan_dissect.h>
20 | #include <epan/color_filters.h>
21 | #include <epan/dissectors/packet-frame.h>
22 | #include <cfile.h>
23 |
24 | #include <ui/qt/utils/color_utils.h>
25 | #include <ui/qt/utils/variant_pointer.h>
26 | #include <ui/qt/utils/wireshark_mime_data.h>
27 | #include <ui/qt/widgets/drag_label.h>
28 | #include <ui/qt/widgets/wireshark_file_dialog.h>
29 | #include <ui/qt/show_packet_bytes_dialog.h>
30 | #include <ui/qt/filter_action.h>
31 | #include <ui/qt/follow_stream_action.h>
32 | #include <ui/qt/main_window.h>
33 | #include <ui/qt/io_graph_action.h>
34 | #include <ui/qt/plot_action.h>
35 | #include <ui/qt/protocol_preferences_menu.h>
36 | #include <ui/qt/models/pref_models.h>
37 | #include <ui/all_files_wildcard.h>
38 | #include <ui/urls.h>
39 | #include "main_application.h"
40 |
41 | #include <QApplication>
42 | #include <QContextMenuEvent>
43 | #include <QDesktopServices>
44 | #include <QHeaderView>
45 | #include <QItemSelectionModel>
46 | #include <QScrollBar>
47 | #include <QStack>
48 | #include <QUrl>
49 | #include <QClipboard>
50 | #include <QWindow>
51 | #include <QMessageBox>
52 | #include <QJsonDocument>
53 | #include <QJsonObject>
54 |
55 | // To do:
56 | // - Fix "apply as filter" behavior.
57 |
58 | ProtoTree::ProtoTree(QWidget *parent, epan_dissect_t *edt_fixed) :
59 | QTreeView(parent),
60 | proto_tree_model_(new ProtoTreeModel(this)),
61 | column_resize_timer_(0),
62 | cap_file_(NULL__null),
63 | edt_(edt_fixed)
64 | {
65 | setAccessibleName(tr("Packet details"));
66 | setAccessibleDescription(tr("Tree view of the selected packet's fields"));
67 | // Leave the uniformRowHeights property as-is (false) since items might have
68 | // have multiple lines (e.g. packet or event comments). If this slows things
69 | // down too much we should add a custom delegate which handles SizeHintRole.
70 | setHeaderHidden(true);
71 |
72 | setStyleSheet(QStringLiteral((QString(QtPrivate::qMakeStringPrivate(u"" "QTreeView:item:hover {" " background-color: %1;" " color: palette(text);" "}")))
73 | "QTreeView:item:hover {"(QString(QtPrivate::qMakeStringPrivate(u"" "QTreeView:item:hover {" " background-color: %1;" " color: palette(text);" "}")))
74 | " background-color: %1;"(QString(QtPrivate::qMakeStringPrivate(u"" "QTreeView:item:hover {" " background-color: %1;" " color: palette(text);" "}")))
75 | " color: palette(text);"(QString(QtPrivate::qMakeStringPrivate(u"" "QTreeView:item:hover {" " background-color: %1;" " color: palette(text);" "}")))
76 | "}")(QString(QtPrivate::qMakeStringPrivate(u"" "QTreeView:item:hover {" " background-color: %1;" " color: palette(text);" "}"))).arg(ColorUtils::hoverBackground().name(QColor::HexArgb)));
77 |
78 | // Shrink down to a small but nonzero size in the main splitter.
79 | int one_em = fontMetrics().height();
80 | setMinimumSize(one_em, one_em);
81 |
82 | setModel(proto_tree_model_);
83 |
84 | connect(this, &ProtoTree::expanded, this, &ProtoTree::syncExpanded);
85 | connect(this, &ProtoTree::collapsed, this, &ProtoTree::syncCollapsed);
86 | connect(this, &ProtoTree::clicked, this, &ProtoTree::itemClicked);
87 | connect(this, &ProtoTree::doubleClicked, this, &ProtoTree::itemDoubleClicked);
88 |
89 | // resizeColumnToContents checks 1000 items by default. The user might
90 | // have scrolled to an area with a different width at this point.
91 | connect(verticalScrollBar(), &QScrollBar::sliderReleased, this, &ProtoTree::updateContentWidth);
92 |
93 | connect(mainApp, &MainApplication::appInitialized, this, &ProtoTree::connectToMainWindow);
94 |
95 | viewport()->installEventFilter(this);
96 | }
97 |
98 | void ProtoTree::clear() {
99 | proto_tree_model_->setRootNode(NULL__null);
100 | updateContentWidth();
101 | }
102 |
103 | void ProtoTree::connectToMainWindow()
104 | {
105 | if (mainApp->mainWindow())
106 | {
107 | connect(mainApp->mainWindow(), &MainWindow::fieldSelected,
108 | this, &ProtoTree::selectedFieldChanged);
109 | connect(mainApp->mainWindow(), &MainWindow::framesSelected,
110 | this, &ProtoTree::selectedFrameChanged);
111 | }
112 | }
113 |
114 | void ProtoTree::ctxCopyVisibleItems()
115 | {
116 | bool selected_tree = false;
117 |
118 | QAction * send = qobject_cast<QAction *>(sender());
119 | if (send && send->property("selected_tree").isValid())
120 | selected_tree = true;
121 |
122 | QString clip;
123 | if (selected_tree && selectionModel()->hasSelection())
124 | clip = toString(selectionModel()->selectedIndexes().first());
125 | else
126 | clip = toString();
127 |
128 | if (clip.length() > 0)
129 | mainApp->clipboard()->setText(clip);
130 | }
131 |
132 | void ProtoTree::ctxCopyAsFilter()
133 | {
134 | QModelIndex idx = selectionModel()->selectedIndexes().first();
135 | FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx));
136 | if (finfo.isValid())
137 | {
138 | epan_dissect_t *edt = cap_file_ ? cap_file_->edt : edt_;
139 | char *field_filter = proto_construct_match_selected_string(finfo.fieldInfo(), edt);
140 | QString filter(field_filter);
141 | wmem_free(Q_NULLPTRnullptr, field_filter);
142 |
143 | if (filter.length() > 0)
144 | mainApp->clipboard()->setText(filter);
145 | }
146 | }
147 |
148 | void ProtoTree::ctxCopySelectedInfo()
149 | {
150 | int val = -1;
151 | QString clip;
152 | QAction * send = qobject_cast<QAction *>(sender());
153 | if (send && send->property("field_type").isValid())
154 | val = send->property("field_type").toInt();
155 |
156 | QModelIndex idx = selectionModel()->selectedIndexes().first();
157 | FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx));
158 | if (! finfo.isValid())
159 | return;
160 |
161 | switch (val)
162 | {
163 | case ProtoTree::Name:
164 | clip.append(finfo.headerInfo().abbreviation);
165 | break;
166 |
167 | case ProtoTree::Description:
168 | clip = idx.data(Qt::DisplayRole).toString();
169 | break;
170 |
171 | case ProtoTree::Value:
172 | {
173 | epan_dissect_t *edt = cap_file_ ? cap_file_->edt : edt_;
174 | char* field_str = get_node_field_value(finfo.fieldInfo(), edt);
175 | clip.append(field_str[0] ? field_str : "(null)");
176 | g_free(field_str);
177 | }
178 | break;
179 | default:
180 | break;
181 | }
182 |
183 | if (clip.length() > 0)
184 | mainApp->clipboard()->setText(clip);
185 | }
186 |
187 | void ProtoTree::ctxOpenUrlWiki()
188 | {
189 | QString url;
190 | bool is_field_reference = false;
191 | QAction * send = qobject_cast<QAction *>(sender());
192 | if (send && send->property("field_reference").isValid())
193 | is_field_reference = send->property("field_reference").toBool();
194 | QModelIndex idx = selectionModel()->selectedIndexes().first();
195 | FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx));
196 |
197 | int field_id = finfo.headerInfo().id;
198 | bool protocol_field_selected = false;
199 | if (!proto_registrar_is_protocol(field_id) && (field_id != hf_text_only)) {
200 | protocol_field_selected = true;
201 | field_id = proto_registrar_get_parent(field_id);
202 | }
203 | const QString proto_abbrev = proto_registrar_get_abbrev(field_id);
204 |
205 | if (! is_field_reference)
206 | {
207 | int ret = QMessageBox::question(this, mainApp->windowTitleString(tr("Wiki Page for %1").arg(proto_abbrev)),
208 | tr("<p>The Wireshark Wiki is maintained by the community.</p>"
209 | "<p>The page you are about to load might be wonderful, "
210 | "incomplete, wrong, or nonexistent.</p>"
211 | "<p>Proceed to the wiki?</p>"),
212 | QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
213 |
214 | if (ret != QMessageBox::Yes) return;
215 |
216 | url = QString(WS_WIKI_URL("Protocols/%1")"https://wiki.wireshark.org" "/" "Protocols/%1").arg(proto_abbrev);
217 | }
218 | else
219 | {
220 | if (field_id != hf_text_only) {
221 | url = QString(WS_DOCS_URL"https://www.wireshark.org/docs/" "dfref/%1/%2.html")
222 | .arg(proto_abbrev[0])
223 | .arg(proto_abbrev);
224 |
225 | if (protocol_field_selected)
226 | {
227 | const QString proto_field_abbrev = proto_registrar_get_abbrev(finfo.headerInfo().id);
228 | url.append(QStringLiteral("#%1")(QString(QtPrivate::qMakeStringPrivate(u"" "#%1"))).arg(proto_field_abbrev));
229 | }
230 | } else {
231 | QMessageBox::information(this, tr("Not a field or protocol"),
232 | tr("No field reference available for text labels."),
233 | QMessageBox::Ok);
234 | }
235 | }
236 |
237 | QDesktopServices::openUrl(url);
238 | }
239 |
240 | void ProtoTree::contextMenuEvent(QContextMenuEvent *event)
241 | {
242 | QModelIndex index = indexAt(event->pos());
243 | if (! index.isValid())
244 | return;
245 |
246 | // We're in a PacketDialog
247 | bool buildForDialog = false;
248 | if (! window()->findChild<QAction *>("actionViewExpandSubtrees"))
249 | buildForDialog = true;
250 |
251 | QMenu * ctx_menu = new QMenu(this);
252 | ctx_menu->setAttribute(Qt::WA_DeleteOnClose);
253 | ctx_menu->setProperty("toolTipsVisible", QVariant::fromValue(true));
254 |
255 | QMenu *main_menu_item, *submenu;
256 | QAction *action;
257 |
258 | bool have_subtree = false;
259 | FieldInformation *finfo = new FieldInformation(proto_tree_model_->protoNodeFromIndex(index), ctx_menu);
260 | field_info * fi = finfo->fieldInfo();
261 | bool is_selected = false;
262 | epan_dissect_t *edt = cap_file_ ? cap_file_->edt : edt_;
263 |
264 | if (cap_file_ && cap_file_->finfo_selected == fi)
265 | is_selected = true;
266 | else if (! window()->findChild<QAction *>("actionViewExpandSubtrees"))
267 | is_selected = true;
268 |
269 | if (is_selected)
270 | {
271 | if (fi && fi->tree_type != -1) {
272 | have_subtree = true;
273 | }
274 | }
275 |
276 | action = ctx_menu->addAction(tr("Expand Subtrees"), this, &ProtoTree::expandSubtrees);
277 | action->setEnabled(have_subtree);
278 | action = ctx_menu->addAction(tr("Collapse Subtrees"), this, &ProtoTree::collapseSubtrees);
279 | action->setEnabled(have_subtree);
280 | ctx_menu->addAction(tr("Expand All"), this, &ProtoTree::expandAll);
281 | ctx_menu->addAction(tr("Collapse All"), this, &ProtoTree::collapseAll);
282 | ctx_menu->addSeparator();
283 |
284 | if (! buildForDialog)
285 | {
286 | if (finfo->headerInfo().type == FT_IPv4 || finfo->headerInfo().type == FT_IPv6) {
287 | action = window()->findChild<QAction *>("actionViewEditResolvedName");
288 | ctx_menu->addAction(action);
289 | ctx_menu->addSeparator();
290 | }
291 | action = window()->findChild<QAction *>("actionAnalyzeApplyAsColumn");
292 | ctx_menu->addAction(action);
293 | ctx_menu->addSeparator();
294 | }
295 |
296 | char * selectedfilter = proto_construct_match_selected_string(finfo->fieldInfo(), edt);
297 | bool can_match_selected = proto_can_match_selected(finfo->fieldInfo(), edt);
298 | ctx_menu->addMenu(FilterAction::createFilterMenu(FilterAction::ActionApply, selectedfilter, can_match_selected, ctx_menu));
299 | ctx_menu->addMenu(FilterAction::createFilterMenu(FilterAction::ActionPrepare, selectedfilter, can_match_selected, ctx_menu));
300 | if (selectedfilter)
301 | wmem_free(Q_NULLPTRnullptr, selectedfilter);
302 |
303 | if (! buildForDialog)
304 | {
305 | QMenu *main_conv_menu = window()->findChild<QMenu *>("menuConversationFilter");
306 | conv_menu_.setTitle(main_conv_menu->title());
307 | conv_menu_.clear();
308 | foreach (QAction *action, main_conv_menu->actions())for (auto _container_308 = QtPrivate::qMakeForeachContainer(main_conv_menu ->actions()); _container_308.i != _container_308.e; ++_container_308 .i) if (QAction *action = *_container_308.i; false) {} else {
309 | conv_menu_.addAction(action);
310 | }
311 |
312 | ctx_menu->addMenu(&conv_menu_);
313 |
314 | colorize_menu_.setTitle(tr("Colorize with Filter"));
315 | ctx_menu->addMenu(&colorize_menu_);
316 |
317 | /* XXX: Should we just get a Follow action (if it exists) for the currently
318 | * selected field info, similar to preferences and filters?
319 | */
320 | main_menu_item = window()->findChild<QMenu *>("menuFollow");
321 | if (main_menu_item) {
322 | submenu = new QMenu(main_menu_item->title(), ctx_menu);
323 | ctx_menu->addMenu(submenu);
324 | foreach (FollowStreamAction *follow_action, main_menu_item->findChildren<FollowStreamAction *>())for (auto _container_324 = QtPrivate::qMakeForeachContainer(main_menu_item ->findChildren<FollowStreamAction *>()); _container_324 .i != _container_324.e; ++_container_324.i) if (FollowStreamAction *follow_action = *_container_324.i; false) {} else {
325 | if (follow_action->isEnabled()) {
326 | submenu->addAction(follow_action);
327 | }
328 | }
329 | }
330 |
331 | ctx_menu->addSeparator();
332 | }
333 |
334 | ctx_menu->addMenu(IOGraphAction::createMenu(finfo->headerInfo(), ctx_menu));
335 |
336 | ctx_menu->addMenu(PlotAction::createMenu(finfo->headerInfo(), ctx_menu));
337 |
338 | submenu = ctx_menu->addMenu(tr("Copy"));
339 | submenu->setToolTipsVisible(true);
340 | submenu->addAction(tr("All Visible Items"), this, &ProtoTree::ctxCopyVisibleItems);
341 | action = submenu->addAction(tr("All Visible Selected Tree Items"), this, &ProtoTree::ctxCopyVisibleItems);
342 | action->setProperty("selected_tree", QVariant::fromValue(true));
343 | action = submenu->addAction(tr("Description"), this, &ProtoTree::ctxCopySelectedInfo);
344 | action->setProperty("field_type", ProtoTree::Description);
345 | action = submenu->addAction(tr("Field Name"), this, &ProtoTree::ctxCopySelectedInfo);
346 | action->setProperty("field_type", ProtoTree::Name);
347 | action = submenu->addAction(tr("Value"), this, &ProtoTree::ctxCopySelectedInfo);
348 | action->setProperty("field_type", ProtoTree::Value);
349 | submenu->addSeparator();
350 | submenu->addAction(tr("As Filter"), this, &ProtoTree::ctxCopyAsFilter);
351 | submenu->addSeparator();
352 | QActionGroup * copyEntries = DataPrinter::copyActions(this, finfo);
353 | submenu->addActions(copyEntries->actions());
354 | ctx_menu->addSeparator();
355 |
356 | if (! buildForDialog)
357 | {
358 | action = window()->findChild<QAction *>("actionAnalyzeShowPacketBytes");
359 | ctx_menu->addAction(action);
360 | action = window()->findChild<QAction *>("actionFileExportPacketBytes");
361 | if (action) {
362 | ctx_menu->addAction(action);
363 | }
364 |
365 | ctx_menu->addSeparator();
366 | }
367 |
368 | int field_id = finfo->headerInfo().id;
369 | bool protocol_field_selected = false;
370 | if (!proto_registrar_is_protocol(field_id) && (field_id != hf_text_only)) {
371 | protocol_field_selected = true;
372 | field_id = proto_registrar_get_parent(field_id);
373 | }
374 | action = ctx_menu->addAction(tr("Wiki Protocol Page"), this, &ProtoTree::ctxOpenUrlWiki);
375 | action->setProperty("toolTip", QString(WS_WIKI_URL("Protocols/%1")"https://wiki.wireshark.org" "/" "Protocols/%1").arg(proto_registrar_get_abbrev(field_id)));
376 |
377 | action = ctx_menu->addAction(tr("Filter Field Reference"), this, &ProtoTree::ctxOpenUrlWiki);
378 | action->setProperty("field_reference", QVariant::fromValue(true));
379 | if (field_id != hf_text_only) {
380 | action->setEnabled(true);
381 | const QString proto_abbrev = proto_registrar_get_abbrev(field_id);
382 | QString url = QString(WS_DOCS_URL"https://www.wireshark.org/docs/" "dfref/%1/%2.html")
383 | .arg(proto_abbrev[0])
384 | .arg(proto_abbrev);
385 |
386 | if (protocol_field_selected)
387 | {
388 | const QString proto_field_abbrev = proto_registrar_get_abbrev(finfo->headerInfo().id);
389 | url.append(QStringLiteral("#%1")(QString(QtPrivate::qMakeStringPrivate(u"" "#%1"))).arg(proto_field_abbrev));
390 | }
391 | action->setProperty("toolTip", url);
392 | }
393 | else {
394 | action->setEnabled(false);
395 | action->setProperty("toolTip", tr("No field reference available for text labels."));
396 | }
397 |
398 | // The "text only" header field will not give preferences for the selected protocol.
399 | // Use parent in this case.
400 | ProtoNode *node = proto_tree_model_->protoNodeFromIndex(index);
401 | while (node && node->isValid() && node->protoNode()->finfo && node->protoNode()->finfo->hfinfo && node->protoNode()->finfo->hfinfo->id == hf_text_only) {
402 | node = node->parentNode();
403 | }
404 |
405 | FieldInformation pref_finfo(node);
406 |
407 | ProtocolPreferencesMenu *proto_prefs_menu = new ProtocolPreferencesMenu(ctx_menu);
408 | proto_prefs_menu->setModule(pref_finfo.moduleName());
409 |
410 | connect(proto_prefs_menu, &ProtocolPreferencesMenu::showProtocolPreferences,
411 | this, &ProtoTree::showProtocolPreferences);
412 | connect(proto_prefs_menu, SIGNAL(editProtocolPreference(pref_t*,module_t*))qFlagLocation("2" "editProtocolPreference(pref_t*,module_t*)" "\0" "ui/qt/proto_tree.cpp" ":" "412"),
413 | this, SIGNAL(editProtocolPreference(pref_t*,module_t*))qFlagLocation("2" "editProtocolPreference(pref_t*,module_t*)" "\0" "ui/qt/proto_tree.cpp" ":" "413"));
414 |
415 | ctx_menu->addMenu(proto_prefs_menu);
416 |
417 | // Add actions for coloring rule fields
418 | bool is_color_rule_name = fi && fi->hfinfo &&
419 | strcmp(fi->hfinfo->abbrev, "frame.coloring_rule.name") == 0;
420 | bool is_color_rule_string = fi && fi->hfinfo &&
421 | strcmp(fi->hfinfo->abbrev, "frame.coloring_rule.string") == 0;
422 |
423 | if (is_color_rule_name || is_color_rule_string) {
424 | // "Coloring Rule Preferences..." positioned right below Protocol Preferences
425 | action = ctx_menu->addAction(tr("Coloring Rule Preferences..."), [this]() {
| Value stored to 'action' is never read
426 | emit showProtocolPreferences(PrefsModel::typeToString(PrefsModel::Layout));
427 | });
428 |
429 | // "Coloring Rules..." opens the View -> Coloring Rules dialog
430 | action = ctx_menu->addAction(tr("Coloring Rules..."), [this]() {
431 | QAction *coloring_rules_action = window()->findChild<QAction *>("actionViewColoringRules");
432 | if (coloring_rules_action)
433 | coloring_rules_action->trigger();
434 | });
435 |
436 | // Pause/Resume and Resume All (only when multi-color details is enabled)
437 | if (prefs.gui_packet_list_multi_color_details) {
438 | QString filter_name;
439 | if (is_color_rule_name) {
440 | filter_name = finfo->toString();
441 | } else {
442 | // frame.coloring_rule.string: the name field is always the previous sibling
443 | QModelIndex nameIdx = index.sibling(index.row() - 1, 0);
444 | if (nameIdx.isValid()) {
445 | FieldInformation nameInfo(proto_tree_model_->protoNodeFromIndex(nameIdx), nullptr);
446 | filter_name = nameInfo.toString();
447 | }
448 | }
449 | if (filter_name.startsWith("[PAUSED] ")) {
450 | filter_name = filter_name.mid(9);
451 | }
452 |
453 | if (!filter_name.isEmpty()) {
454 | bool is_paused = color_filter_is_session_disabled(filter_name.toUtf8().constData());
455 | QString action_text = is_paused ? tr("Resume Coloring Rule") : tr("Pause Coloring Rule");
456 |
457 | action = ctx_menu->addAction(action_text, [this, filter_name, is_paused]() {
458 | color_filter_set_session_disabled(filter_name.toUtf8().constData(), !is_paused);
459 | QTimer::singleShot(0, this, [this]() {
460 | emit redissectPacketsRequested();
461 | });
462 | });
463 |
464 | action = ctx_menu->addAction(tr("Resume All Coloring Rules"), [this]() {
465 | color_filter_resume_all(NULL__null);
466 | QTimer::singleShot(0, this, [this]() {
467 | emit redissectPacketsRequested();
468 | });
469 | });
470 | }
471 | }
472 |
473 | }
474 |
475 | ctx_menu->addSeparator();
476 |
477 | if (! buildForDialog)
478 | {
479 | QAction *decode_as = window()->findChild<QAction *>("actionAnalyzeDecodeAs");
480 | if (decode_as) {
481 | ctx_menu->addAction(decode_as);
482 | decode_as->setProperty("create_new", QVariant::fromValue(true));
483 |
484 | ctx_menu->addAction(window()->findChild<QAction *>("actionGoGoToLinkedPacket"));
485 | ctx_menu->addAction(window()->findChild<QAction *>("actionContextShowLinkedPacketInNewWindow"));
486 | }
487 | }
488 |
489 | ctx_menu->popup(event->globalPos());
490 | }
491 |
492 | void ProtoTree::timerEvent(QTimerEvent *event)
493 | {
494 | if (event->timerId() == column_resize_timer_) {
495 | killTimer(column_resize_timer_);
496 | column_resize_timer_ = 0;
497 | resizeColumnToContents(0);
498 | } else {
499 | QTreeView::timerEvent(event);
500 | }
501 | }
502 |
503 | // resizeColumnToContents checks 1000 items by default. The user might
504 | // have scrolled to an area with a different width at this point.
505 | void ProtoTree::keyReleaseEvent(QKeyEvent *event)
506 | {
507 | if (event->isAutoRepeat()) return;
508 |
509 | switch(event->key()) {
510 | case Qt::Key_Up:
511 | case Qt::Key_Down:
512 | case Qt::Key_PageUp:
513 | case Qt::Key_PageDown:
514 | case Qt::Key_Home:
515 | case Qt::Key_End:
516 | updateContentWidth();
517 | break;
518 | default:
519 | break;
520 | }
521 | }
522 |
523 | void ProtoTree::updateContentWidth()
524 | {
525 | if (column_resize_timer_ == 0) {
526 | column_resize_timer_ = startTimer(0);
527 | }
528 | }
529 |
530 | void ProtoTree::setMonospaceFont(const QFont &mono_font)
531 | {
532 | setFont(mono_font);
533 | update();
534 | }
535 |
536 | void ProtoTree::foreachTreeNode(proto_node *node, void *proto_tree_ptr)
537 | {
538 | ProtoTree *tree_view = static_cast<ProtoTree *>(proto_tree_ptr);
539 | ProtoTreeModel *model = qobject_cast<ProtoTreeModel *>(tree_view->model());
540 | if (!tree_view || !model) {
541 | return;
542 | }
543 |
544 | // Related frames - there might be hidden FT_FRAMENUM nodes, so do this
545 | // for each proto_node and not just the ProtoNodes in the model
546 | if (node->finfo->hfinfo->type == FT_FRAMENUM) {
547 | ft_framenum_type_t framenum_type = (ft_framenum_type_t)GPOINTER_TO_INT(node->finfo->hfinfo->strings)((gint) (glong) (node->finfo->hfinfo->strings));
548 | tree_view->emitRelatedFrame(fvalue_get_uinteger(node->finfo->value), framenum_type);
549 | }
550 |
551 | proto_tree_children_foreach(node, foreachTreeNode, proto_tree_ptr);
552 | }
553 |
554 | // NOLINTNEXTLINE(misc-no-recursion)
555 | void ProtoTree::foreachExpand(const QModelIndex &index = QModelIndex()) {
556 |
557 | // Restore expanded state. (Note QModelIndex() refers to the root node)
558 | int children = proto_tree_model_->rowCount(index);
559 | QModelIndex childIndex;
560 | for (int child = 0; child < children; child++) {
561 | childIndex = proto_tree_model_->index(child, 0, index);
562 | if (childIndex.isValid()) {
563 | ProtoNode *node = proto_tree_model_->protoNodeFromIndex(childIndex);
564 | if (node && node->isValid() && tree_expanded(node->protoNode()->finfo->tree_type)) {
565 | expand(childIndex);
566 | }
567 | // We recurse here, but we're limited by tree depth checks in epan
568 | foreachExpand(childIndex);
569 | }
570 | }
571 | }
572 |
573 | // setRootNode sets the new contents for the protocol tree and subsequently
574 | // restores the previously expanded state.
575 | void ProtoTree::setRootNode(proto_node *root_node) {
576 | // We track item expansion using proto.c:tree_is_expanded.
577 | // Replace any existing (possibly invalidated) proto tree by the new tree.
578 | // The expanded state will be reset as well and will be re-expanded below.
579 | proto_tree_model_->setRootNode(root_node);
580 |
581 | disconnect(this, &ProtoTree::expanded, this, &ProtoTree::syncExpanded);
582 | proto_tree_children_foreach(root_node, foreachTreeNode, this);
583 | foreachExpand();
584 | connect(this, &ProtoTree::expanded, this, &ProtoTree::syncExpanded);
585 |
586 | updateContentWidth();
587 | }
588 |
589 | void ProtoTree::emitRelatedFrame(int related_frame, ft_framenum_type_t framenum_type)
590 | {
591 | emit relatedFrame(related_frame, framenum_type);
592 | }
593 |
594 | void ProtoTree::autoScrollTo(const QModelIndex &index)
595 | {
596 | selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
597 | if (!index.isValid()) {
598 | return;
599 | }
600 |
601 | // ensure item is visible (expanding its parents as needed).
602 | scrollTo(index);
603 | }
604 |
605 | // XXX We select the first match, which might not be the desired item.
606 | void ProtoTree::goToHfid(int hfid)
607 | {
608 | QModelIndex index = proto_tree_model_->findFirstHfid(hfid);
609 | autoScrollTo(index);
610 | }
611 |
612 | void ProtoTree::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
613 | {
614 | QTreeView::selectionChanged(selected, deselected);
615 | if (selected.isEmpty()) {
616 | emit fieldSelected(0);
617 | return;
618 | }
619 |
620 | QModelIndex index = selected.indexes().first();
621 | saveSelectedField(index);
622 |
623 | // Find and highlight the protocol bytes. select above won't call
624 | // selectionChanged if the current and selected indexes are the same
625 | // so we do this here.
626 | FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index), this);
627 | if (finfo.isValid()) {
628 | QModelIndex parent = index;
629 | while (parent.isValid() && parent.parent().isValid()) {
630 | parent = parent.parent();
631 | }
632 | if (parent.isValid()) {
633 | FieldInformation parent_finfo(proto_tree_model_->protoNodeFromIndex(parent));
634 | finfo.setParentField(parent_finfo.fieldInfo());
635 | }
636 | emit fieldSelected(&finfo);
637 | }
638 | }
639 |
640 | void ProtoTree::syncExpanded(const QModelIndex &index) {
641 | FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index));
642 | if (!finfo.isValid()) return;
643 |
644 | /*
645 | * Nodes with "finfo->tree_type" of -1 have no ett_ value, and
646 | * are thus presumably leaf nodes and cannot be expanded.
647 | */
648 | if (finfo.treeType() != -1) {
649 | tree_expanded_set(finfo.treeType(), true);
650 | }
651 | }
652 |
653 | void ProtoTree::syncCollapsed(const QModelIndex &index) {
654 | FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index));
655 | if (!finfo.isValid()) return;
656 |
657 | /*
658 | * Nodes with "finfo->tree_type" of -1 have no ett_ value, and
659 | * are thus presumably leaf nodes and cannot be collapsed.
660 | */
661 | if (finfo.treeType() != -1) {
662 | tree_expanded_set(finfo.treeType(), false);
663 | }
664 | }
665 |
666 | void ProtoTree::expandSubtrees()
667 | {
668 | if (!selectionModel()->hasSelection()) return;
669 |
670 | QStack<QModelIndex> index_stack;
671 | index_stack.push(selectionModel()->selectedIndexes().first());
672 |
673 | while (!index_stack.isEmpty()) {
674 | QModelIndex index = index_stack.pop();
675 | expand(index);
676 | int row_count = proto_tree_model_->rowCount(index);
677 | for (int row = row_count - 1; row >= 0; row--) {
678 | QModelIndex child = proto_tree_model_->index(row, 0, index);
679 | if (proto_tree_model_->hasChildren(child)) {
680 | index_stack.push(child);
681 | }
682 | }
683 | }
684 |
685 | updateContentWidth();
686 | }
687 |
688 | void ProtoTree::collapseSubtrees()
689 | {
690 | if (!selectionModel()->hasSelection()) return;
691 |
692 | QStack<QModelIndex> index_stack;
693 | index_stack.push(selectionModel()->selectedIndexes().first());
694 |
695 | while (!index_stack.isEmpty()) {
696 | QModelIndex index = index_stack.pop();
697 | collapse(index);
698 | int row_count = proto_tree_model_->rowCount(index);
699 | for (int row = row_count - 1; row >= 0; row--) {
700 | QModelIndex child = proto_tree_model_->index(row, 0, index);
701 | if (proto_tree_model_->hasChildren(child)) {
702 | index_stack.push(child);
703 | }
704 | }
705 | }
706 |
707 | updateContentWidth();
708 | }
709 |
710 | void ProtoTree::expandAll()
711 | {
712 | for (int i = 0; i < num_tree_types; i++) {
713 | tree_expanded_set(i, true);
714 | }
715 | QTreeView::expandAll();
716 | updateContentWidth();
717 | }
718 |
719 | void ProtoTree::collapseAll()
720 | {
721 | for (int i = 0; i < num_tree_types; i++) {
722 | tree_expanded_set(i, false);
723 | }
724 | QTreeView::collapseAll();
725 | updateContentWidth();
726 | }
727 |
728 | void ProtoTree::itemClicked(const QModelIndex &index)
729 | {
730 | // selectionChanged() is not issued when some action would select
731 | // the same item as currently selected - but we want to make sure
732 | // HexDataSourceView is highlighting that field. The BVT highlighted bytes
733 | // might be different, due to hover highlighting or Find Packet "bytes".
734 | //
735 | // Unfortunately, clicked() is singled after selectionChanged(), so
736 | // we emit fieldSelected() twice after clicking on a new frame, once
737 | // in selectionChanged(), and once here.
738 | //
739 | // We can't get rid of the fieldSelected() handling because there are
740 | // non-mouse event ways to select a field, such as keyboard navigation.
741 | //
742 | // All this would be easier if Qt had a signal similar to
743 | // selectionChanged() that was triggered even if the new selection
744 | // was the same as the old one.
745 | if (selectionModel()->selectedIndexes().isEmpty()) {
746 | emit fieldSelected(0);
747 | } else if (index == selectionModel()->selectedIndexes().first()) {
748 | FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index), this);
749 |
750 | if (finfo.isValid()) {
751 | // Find and highlight the protocol bytes.
752 | QModelIndex parent = index;
753 | while (parent.isValid() && parent.parent().isValid()) {
754 | parent = parent.parent();
755 | }
756 | if (parent.isValid()) {
757 | FieldInformation parent_finfo(proto_tree_model_->protoNodeFromIndex(parent));
758 | finfo.setParentField(parent_finfo.fieldInfo());
759 | }
760 | emit fieldSelected(&finfo);
761 | }
762 | }
763 | }
764 |
765 | void ProtoTree::itemDoubleClicked(const QModelIndex &index)
766 | {
767 | FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index));
768 | if (!finfo.isValid()) return;
769 |
770 | if (finfo.headerInfo().type == FT_FRAMENUM) {
771 | if (QApplication::queryKeyboardModifiers() & Qt::ShiftModifier) {
772 | emit openPacketInNewWindow(true);
773 | } else {
774 | mainApp->gotoFrame(fvalue_get_uinteger(finfo.fieldInfo()->value));
775 | }
776 | } else {
777 | QString url = finfo.url();
778 | if (!url.isEmpty()) {
779 | QApplication::clipboard()->setText(url);
780 | QString push_msg = tr("Copied %1").arg(url);
781 | mainApp->pushStatus(MainApplication::TemporaryStatus, push_msg);
782 | }
783 | }
784 | }
785 |
786 | void ProtoTree::selectedFrameChanged(QList<int> frames)
787 | {
788 | if (frames.count() == 1 && cap_file_ && cap_file_->edt && cap_file_->edt->tree) {
789 | setRootNode(cap_file_->edt->tree);
790 | } else {
791 | // Clear the proto tree contents as they have become invalid.
792 | proto_tree_model_->setRootNode(NULL__null);
793 | }
794 | }
795 |
796 | // Select a field and bring it into view. Intended to be called by external
797 | // components (such as the byte view).
798 | void ProtoTree::selectedFieldChanged(FieldInformation *finfo)
799 | {
800 | if (finfo && finfo->parent() == this) {
801 | // We only want inbound signals.
802 | return;
803 | }
804 |
805 | QModelIndex index = proto_tree_model_->findFieldInformation(finfo);
806 | setUpdatesEnabled(false);
807 | // The new finfo might match the current index. Clear our selection
808 | // so that we force a fresh item selection, so that fieldSelected
809 | // will in turn be emitted.
810 | selectionModel()->clearSelection();
811 | autoScrollTo(index);
812 | setUpdatesEnabled(true);
813 | }
814 |
815 | // Remember the currently focussed field based on:
816 | // - current hf_id (obviously)
817 | // - parent items (to avoid selecting a text item in a different tree)
818 | // - the row of each item
819 | void ProtoTree::saveSelectedField(QModelIndex &index)
820 | {
821 | selected_hfid_path_.clear();
822 | QModelIndex save_index = index;
823 | while (save_index.isValid()) {
824 | FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(save_index));
825 | if (!finfo.isValid()) break;
826 | selected_hfid_path_.prepend(QPair<int,int>(save_index.row(), finfo.headerInfo().id));
827 | save_index = save_index.parent();
828 | }
829 | }
830 |
831 | // Try to focus a tree item which was previously also visible
832 | void ProtoTree::restoreSelectedField()
833 | {
834 | if (selected_hfid_path_.isEmpty()) return;
835 |
836 | QModelIndex cur_index = QModelIndex();
837 | QPair<int,int> path_entry;
838 | foreach (path_entry, selected_hfid_path_)for (auto _container_838 = QtPrivate::qMakeForeachContainer(selected_hfid_path_ ); _container_838.i != _container_838.e; ++_container_838.i) if (path_entry = *_container_838.i; false) {} else {
839 | int row = path_entry.first;
840 | int hf_id = path_entry.second;
841 | cur_index = proto_tree_model_->index(row, 0, cur_index);
842 | FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(cur_index));
843 | if (!finfo.isValid() || finfo.headerInfo().id != hf_id) {
844 | // Did not find the selected hfid path in the selected packet
845 | cur_index = QModelIndex();
846 | emit fieldSelected(0);
847 | break;
848 | }
849 | }
850 |
851 | autoScrollTo(cur_index);
852 | }
853 |
854 | // NOLINTNEXTLINE(misc-no-recursion)
855 | QString ProtoTree::traverseTree(const QModelIndex & travTree, int identLevel) const
856 | {
857 | QString result = "";
858 |
859 | if (travTree.isValid())
860 | {
861 | result.append(QStringLiteral(" ")(QString(QtPrivate::qMakeStringPrivate(u"" " "))).repeated(identLevel));
862 | result.append(travTree.data().toString());
863 | result.append("\n");
864 |
865 | /* if the element is expanded, we traverse one level down */
866 | if (isExpanded(travTree))
867 | {
868 | int children = proto_tree_model_->rowCount(travTree);
869 | identLevel++;
870 | for (int child = 0; child < children; child++)
871 | // We recurse here, but we're limited by tree depth checks in epan
872 | result += traverseTree(proto_tree_model_->index(child, 0, travTree), identLevel);
873 | }
874 | }
875 |
876 | return result;
877 | }
878 |
879 | QString ProtoTree::toString(const QModelIndex &start_idx) const
880 | {
881 | QString tree_string = "";
882 | if (start_idx.isValid())
883 | tree_string = traverseTree(start_idx, 0);
884 | else
885 | {
886 | int children = proto_tree_model_->rowCount();
887 | for (int child = 0; child < children; child++)
888 | tree_string += traverseTree(proto_tree_model_->index(child, 0, QModelIndex()), 0);
889 | }
890 |
891 | return tree_string;
892 | }
893 |
894 | void ProtoTree::setCaptureFile(capture_file *cf)
895 | {
896 | // For use by the main view, set the capture file which will later have a
897 | // dissection (EDT) ready.
898 | // The packet dialog sets a fixed EDT context and MUST NOT use this.
899 | Q_ASSERT(edt_ == NULL)((edt_ == __null) ? static_cast<void>(0) : qt_assert("edt_ == NULL" , "ui/qt/proto_tree.cpp", 899));
900 | cap_file_ = cf;
901 | }
902 |
903 | bool ProtoTree::eventFilter(QObject * obj, QEvent * event)
904 | {
905 | if (event->type() != QEvent::MouseButtonPress && event->type() != QEvent::MouseMove)
906 | return QTreeView::eventFilter(obj, event);
907 |
908 | /* Mouse was over scrollbar, ignoring */
909 | if (qobject_cast<QScrollBar *>(obj))
910 | return QTreeView::eventFilter(obj, event);
911 |
912 | if (event->type() == QEvent::MouseButtonPress)
913 | {
914 | QMouseEvent * ev = (QMouseEvent *)event;
915 |
916 | if (ev->buttons() & Qt::LeftButton)
917 | drag_start_position_ = ev->pos();
918 | }
919 | else if (event->type() == QEvent::MouseMove)
920 | {
921 | QMouseEvent * ev = (QMouseEvent *)event;
922 |
923 | if ((ev->buttons() & Qt::LeftButton) && (ev->pos() - drag_start_position_).manhattanLength()
924 | > QApplication::startDragDistance())
925 | {
926 | QModelIndex idx = indexAt(drag_start_position_);
927 | FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx));
928 | if (finfo.isValid())
929 | {
930 | /* Hack to prevent QItemSelection taking the item which has been dragged over at start
931 | * of drag-drop operation. selectionModel()->blockSignals could have done the trick, but
932 | * it does not take in a QTreeWidget (maybe View) */
933 | emit fieldSelected(&finfo);
934 | selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect);
935 |
936 | epan_dissect_t *edt = cap_file_ ? cap_file_->edt : edt_;
937 | char *field_filter = proto_construct_match_selected_string(finfo.fieldInfo(), edt);
938 | QString filter(field_filter);
939 | wmem_free(NULL__null, field_filter);
940 |
941 | if (filter.length() > 0)
942 | {
943 | QJsonObject filterData;
944 | filterData["filter"] = filter;
945 | filterData["name"] = finfo.headerInfo().abbreviation;
946 | filterData["description"] = finfo.headerInfo().name;
947 | QMimeData * mimeData = new QMimeData();
948 |
949 | mimeData->setData(WiresharkMimeData::DisplayFilterMimeType, QJsonDocument(filterData).toJson());
950 | mimeData->setText(toString(idx));
951 |
952 | QDrag * drag = new QDrag(this);
953 | drag->setMimeData(mimeData);
954 |
955 | QString lblTxt = QStringLiteral("%1\n%2")(QString(QtPrivate::qMakeStringPrivate(u"" "%1\n%2"))).arg(finfo.headerInfo().name, filter);
956 |
957 | DragLabel * content = new DragLabel(lblTxt, this);
958 |
959 | qreal dpr = window()->windowHandle()->devicePixelRatio();
960 | QPixmap pixmap(content->size() * dpr);
961 | pixmap.setDevicePixelRatio(dpr);
962 | content->render(&pixmap);
963 | drag->setPixmap(pixmap);
964 |
965 | drag->exec(Qt::CopyAction);
966 |
967 | return true;
968 | }
969 | }
970 | }
971 | }
972 |
973 | return QTreeView::eventFilter(obj, event);
974 | }
975 |
976 | QModelIndex ProtoTree::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
977 | {
978 | if (cursorAction == MoveLeft && selectionModel()->hasSelection()) {
979 | QModelIndex cur_idx = selectionModel()->selectedIndexes().first();
980 | QModelIndex parent = cur_idx.parent();
981 | if (!isExpanded(cur_idx) && parent.isValid() && parent != rootIndex()) {
982 | return parent;
983 | }
984 | }
985 | return QTreeView::moveCursor(cursorAction, modifiers);
986 | }