diff options
Diffstat (limited to 'ui/qt/wireshark_main_window.cpp')
-rw-r--r-- | ui/qt/wireshark_main_window.cpp | 3250 |
1 files changed, 3250 insertions, 0 deletions
diff --git a/ui/qt/wireshark_main_window.cpp b/ui/qt/wireshark_main_window.cpp new file mode 100644 index 00000000..040c45ba --- /dev/null +++ b/ui/qt/wireshark_main_window.cpp @@ -0,0 +1,3250 @@ +/* main_window.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "main_application.h" +#include "wireshark_main_window.h" + +/* + * The generated Ui_WiresharkMainWindow::setupUi() can grow larger than our configured limit, + * so turn off -Wframe-larger-than= for ui_main_window.h. + */ +DIAG_OFF(frame-larger-than=) +#include <ui_wireshark_main_window.h> +DIAG_ON(frame-larger-than=) + +#include <epan/addr_resolv.h> +#include "epan/conversation_filter.h" +#include <epan/epan_dissect.h> +#include <wsutil/filesystem.h> +#include <wsutil/wslog.h> +#include <wsutil/ws_assert.h> +#include <wsutil/version_info.h> +#include <epan/prefs.h> +#include <epan/stats_tree_priv.h> +#include <epan/plugin_if.h> +#include <epan/export_object.h> +#include <frame_tvbuff.h> + +#include "ui/iface_toolbar.h" + +#ifdef HAVE_LIBPCAP +#include "ui/capture.h" +#include <capture/capture_session.h> +#endif + +#include "ui/alert_box.h" +#ifdef HAVE_LIBPCAP +#include "ui/capture_ui_utils.h" +#endif +#include "ui/capture_globals.h" +#include "ui/main_statusbar.h" +#include "ui/recent.h" +#include "ui/recent_utils.h" +#include "ui/util.h" +#include "ui/preference_utils.h" + +#include "byte_view_tab.h" +#ifdef HAVE_LIBPCAP +#include "capture_options_dialog.h" +#endif +#include "conversation_colorize_action.h" +#include "export_dissection_dialog.h" +#include "export_object_action.h" +#include "file_set_dialog.h" +#include "filter_dialog.h" +#include "follow_stream_action.h" +#include "funnel_statistics.h" +#include "import_text_dialog.h" +#include "interface_toolbar.h" +#include "packet_diagram.h" +#include "packet_list.h" +#include "proto_tree.h" +#include "simple_dialog.h" +#include "tap_parameter_dialog.h" +#include "wireless_frame.h" +#include <ui/qt/widgets/wireless_timeline.h> + +#include <ui/qt/widgets/additional_toolbar.h> +#include <ui/qt/widgets/display_filter_edit.h> +#include <ui/qt/widgets/filter_expression_toolbar.h> + +#include <ui/qt/utils/color_utils.h> +#include <ui/qt/utils/qt_ui_utils.h> +#include <ui/qt/utils/stock_icon.h> +#include <ui/qt/utils/variant_pointer.h> + +#include <QAction> +#include <QActionGroup> +#include <QIntValidator> +#include <QKeyEvent> +#include <QList> +#include <QMessageBox> +#include <QMetaObject> +#include <QMimeData> +#include <QTabWidget> +#include <QTextCodec> +#include <QToolButton> +#include <QTreeWidget> +#include <QUrl> + +//menu_recent_file_write_all + +// If we ever add support for multiple windows this will need to be replaced. +static WiresharkMainWindow *gbl_cur_main_window_ = NULL; + +static void plugin_if_mainwindow_apply_filter(GHashTable * data_set) +{ + if (!gbl_cur_main_window_ || !data_set) + return; + + if (g_hash_table_lookup_extended(data_set, "filter_string", NULL, NULL)) { + QString filter((const char *)g_hash_table_lookup(data_set, "filter_string")); + gbl_cur_main_window_->filterPackets(filter); + } +} + +static void plugin_if_mainwindow_preference(GHashTable * data_set) +{ + if (!gbl_cur_main_window_ || !data_set) + return; + + const char * module_name; + const char * pref_name; + const char * pref_value; + +DIAG_OFF_CAST_AWAY_CONST + if (g_hash_table_lookup_extended(data_set, "pref_module", NULL, (gpointer *)&module_name) && + g_hash_table_lookup_extended(data_set, "pref_key", NULL, (gpointer *)&pref_name) && + g_hash_table_lookup_extended(data_set, "pref_value", NULL, (gpointer *)&pref_value)) + { + unsigned int changed_flags = prefs_store_ext(module_name, pref_name, pref_value); + if (changed_flags) { + mainApp->emitAppSignal(WiresharkApplication::PacketDissectionChanged); + mainApp->emitAppSignal(WiresharkApplication::PreferencesChanged); + } + } +DIAG_ON_CAST_AWAY_CONST +} + +static void plugin_if_mainwindow_gotoframe(GHashTable * data_set) +{ + if (!gbl_cur_main_window_ || !data_set) + return; + + gpointer framenr; + + if (g_hash_table_lookup_extended(data_set, "frame_nr", NULL, &framenr)) { + if (GPOINTER_TO_UINT(framenr) != 0) + gbl_cur_main_window_->gotoFrame(GPOINTER_TO_UINT(framenr)); + } +} + +#ifdef HAVE_LIBPCAP + +static void plugin_if_mainwindow_get_ws_info(GHashTable * data_set) +{ + if (!gbl_cur_main_window_ || !data_set) + return; + + ws_info_t *ws_info = NULL; + + if (!g_hash_table_lookup_extended(data_set, "ws_info", NULL, (void**)&ws_info)) + return; + + CaptureFile *cfWrap = gbl_cur_main_window_->captureFile(); + capture_file *cf = cfWrap->capFile(); + + ws_info->ws_info_supported = true; + + /* If we have a filename attached to ws_info clear it */ + if (ws_info->cf_filename != NULL) + { + g_free(ws_info->cf_filename); + ws_info->cf_filename = NULL; + } + + /* Determine the true state of the capture file. We return the true state in + the ws_info structure and DON'T CHANGE the cf->state as we don't want to cause problems + with code that follows this. */ + if (cf) + { + if (cf->filename) + { + /* As we have a cf->filename we'll use the name and the state */ + ws_info->cf_filename = g_strdup(cf->filename); + ws_info->cf_state = cf->state; + } + else + { + /* When we come through here the cf->state can show FILE_READ_DONE even though the + file is actually closed (no filename). A better fix would be to have a + FILE_CLOSE_PENDING state but that involves a lot of code change elsewhere. */ + ws_info->cf_state = FILE_CLOSED; + } + } + + if (!ws_info->cf_filename) + { + /* We may have a filename associated with the main window so let's use it */ + QString fileNameString = gbl_cur_main_window_->getMwFileName(); + if (fileNameString.length()) + { + QByteArray ba = fileNameString.toLatin1(); + const char *c_file_name = ba.data(); + ws_info->cf_filename = g_strdup(c_file_name); + } + } + + if (cf) { + ws_info->cf_count = cf->count; + + QList<int> rows = gbl_cur_main_window_->selectedRows(); + frame_data * fdata = NULL; + if (rows.count() > 0) + fdata = gbl_cur_main_window_->frameDataForRow(rows.at(0)); + + if (cf->state == FILE_READ_DONE && fdata) { + ws_info->cf_framenr = fdata->num; + ws_info->frame_passed_dfilter = (fdata->passed_dfilter == 1); + } + else { + ws_info->cf_framenr = 0; + ws_info->frame_passed_dfilter = FALSE; + } + } + else + { + /* Initialise the other ws_info structure values */ + ws_info->cf_count = 0; + ws_info->cf_framenr = 0; + ws_info->frame_passed_dfilter = FALSE; + } +} + +#endif /* HAVE_LIBPCAP */ + +static void plugin_if_mainwindow_get_frame_data(GHashTable* data_set) +{ + if (!gbl_cur_main_window_ || !data_set) + return; + + plugin_if_frame_data_cb extract_cb; + void* user_data; + void** ret_value_ptr; + + if (g_hash_table_lookup_extended(data_set, "extract_cb", NULL, (void**)&extract_cb) && + g_hash_table_lookup_extended(data_set, "user_data", NULL, (void**)&user_data) && + g_hash_table_lookup_extended(data_set, "ret_value_ptr", NULL, (void**)&ret_value_ptr)) + { + QList<int> rows = gbl_cur_main_window_->selectedRows(); + if (rows.count() > 0) { + frame_data* fdata = gbl_cur_main_window_->frameDataForRow(rows.at(0)); + if (fdata) { + *ret_value_ptr = extract_cb(fdata, user_data); + } + } + } +} + +static void plugin_if_mainwindow_get_capture_file(GHashTable* data_set) +{ + if (!gbl_cur_main_window_ || !data_set) + return; + + plugin_if_capture_file_cb extract_cb; + void* user_data; + void** ret_value_ptr; + + if (g_hash_table_lookup_extended(data_set, "extract_cb", NULL, (void**)&extract_cb) && + g_hash_table_lookup_extended(data_set, "user_data", NULL, (void**)&user_data) && + g_hash_table_lookup_extended(data_set, "ret_value_ptr", NULL, (void**)&ret_value_ptr)) + { + CaptureFile* cfWrap = gbl_cur_main_window_->captureFile(); + capture_file* cf = cfWrap->capFile(); + if (cf) { + *ret_value_ptr = extract_cb(cf, user_data); + } + } +} + +static void plugin_if_mainwindow_update_toolbars(GHashTable * data_set) +{ + if (!gbl_cur_main_window_ || !data_set) + return; + + if (g_hash_table_lookup_extended(data_set, "toolbar_name", NULL, NULL)) { + QString toolbarName((const char *)g_hash_table_lookup(data_set, "toolbar_name")); + gbl_cur_main_window_->removeAdditionalToolbar(toolbarName); + + } +} + +static void mainwindow_add_toolbar(const iface_toolbar *toolbar_entry) +{ + if (gbl_cur_main_window_ && toolbar_entry) + { + gbl_cur_main_window_->addInterfaceToolbar(toolbar_entry); + } +} + +static void mainwindow_remove_toolbar(const gchar *menu_title) +{ + if (gbl_cur_main_window_ && menu_title) + { + gbl_cur_main_window_->removeInterfaceToolbar(menu_title); + } +} + +QMenu* WiresharkMainWindow::findOrAddMenu(QMenu *parent_menu, QString& menu_text) { + QList<QAction *> actions = parent_menu->actions(); + QList<QAction *>::const_iterator i; + for (i = actions.constBegin(); i != actions.constEnd(); ++i) { + if ((*i)->text()==menu_text) { + return (*i)->menu(); + } + } + // If we get here there menu entry was not found, add a sub menu + return parent_menu->addMenu(menu_text); +} + +WiresharkMainWindow::WiresharkMainWindow(QWidget *parent) : + MainWindow(parent), + main_ui_(new Ui::WiresharkMainWindow), + previous_focus_(NULL), + file_set_dialog_(NULL), + show_hide_actions_(NULL), + time_display_actions_(NULL), + time_precision_actions_(NULL), + funnel_statistics_(NULL), + freeze_focus_(NULL), + was_maximized_(false), + capture_stopping_(false), + capture_filter_valid_(false) +#ifdef HAVE_LIBPCAP + , capture_options_dialog_(NULL) + , info_data_() +#endif +#if defined(Q_OS_MAC) + , dock_menu_(NULL) +#endif +{ + if (!gbl_cur_main_window_) { + connect(mainApp, SIGNAL(openStatCommandDialog(QString, const char*, void*)), + this, SLOT(openStatCommandDialog(QString, const char*, void*))); + connect(mainApp, SIGNAL(openTapParameterDialog(QString, const QString, void*)), + this, SLOT(openTapParameterDialog(QString, const QString, void*))); + } + gbl_cur_main_window_ = this; +#ifdef HAVE_LIBPCAP + capture_input_init(&cap_session_, CaptureFile::globalCapFile()); +#endif + + findTextCodecs(); + // setpUi calls QMetaObject::connectSlotsByName(this). connectSlotsByName + // iterates over *all* of our children, looking for matching "on_" slots. + // The fewer children we have at this point the better. + main_ui_->setupUi(this); +#ifdef HAVE_SOFTWARE_UPDATE + update_action_ = new QAction(tr("Check for Updates…"), main_ui_->menuHelp); +#endif +#if defined(HAVE_LIBNL) && defined(HAVE_NL80211) + wireless_frame_ = new WirelessFrame(this); + main_ui_->wirelessToolBar->addWidget(wireless_frame_); +#else + removeToolBar(main_ui_->wirelessToolBar); + main_ui_->menuView->removeAction(main_ui_->actionViewWirelessToolbar); +#endif + + menu_groups_ = QList<register_stat_group_t>() + << REGISTER_PACKET_ANALYZE_GROUP_UNSORTED + << REGISTER_ANALYZE_GROUP_CONVERSATION_FILTER + << REGISTER_PACKET_STAT_GROUP_UNSORTED + << REGISTER_STAT_GROUP_GENERIC + << REGISTER_STAT_GROUP_CONVERSATION_LIST + << REGISTER_STAT_GROUP_ENDPOINT_LIST + << REGISTER_STAT_GROUP_RESPONSE_TIME + << REGISTER_STAT_GROUP_RSERPOOL + << REGISTER_STAT_GROUP_TELEPHONY + << REGISTER_STAT_GROUP_TELEPHONY_ANSI + << REGISTER_STAT_GROUP_TELEPHONY_GSM + << REGISTER_STAT_GROUP_TELEPHONY_LTE + << REGISTER_STAT_GROUP_TELEPHONY_MTP3 + << REGISTER_STAT_GROUP_TELEPHONY_SCTP + << REGISTER_TOOLS_GROUP_UNSORTED; + + setWindowIcon(mainApp->normalIcon()); + setTitlebarForCaptureFile(); + setMenusForCaptureFile(); + setForCapturedPackets(false); + setMenusForFileSet(false); + interfaceSelectionChanged(); + loadWindowGeometry(); + +#ifndef HAVE_LUA + main_ui_->actionAnalyzeReloadLuaPlugins->setVisible(false); +#endif + + qRegisterMetaType<FilterAction::Action>("FilterAction::Action"); + qRegisterMetaType<FilterAction::ActionType>("FilterAction::ActionType"); + connect(this, SIGNAL(filterAction(QString, FilterAction::Action, FilterAction::ActionType)), + this, SLOT(queuedFilterAction(QString, FilterAction::Action, FilterAction::ActionType)), + Qt::QueuedConnection); + + //To prevent users use features before initialization complete + //Otherwise unexpected problems may occur + setFeaturesEnabled(false); + connect(mainApp, SIGNAL(appInitialized()), this, SLOT(setFeaturesEnabled())); + connect(mainApp, SIGNAL(appInitialized()), this, SLOT(applyGlobalCommandLineOptions())); + connect(mainApp, SIGNAL(appInitialized()), this, SLOT(zoomText())); + connect(mainApp, SIGNAL(appInitialized()), this, SLOT(initViewColorizeMenu())); + connect(mainApp, SIGNAL(appInitialized()), this, SLOT(addStatsPluginsToMenu())); + connect(mainApp, SIGNAL(appInitialized()), this, SLOT(addDynamicMenus())); + connect(mainApp, SIGNAL(appInitialized()), this, SLOT(addPluginIFStructures())); + connect(mainApp, SIGNAL(appInitialized()), this, SLOT(initConversationMenus())); + connect(mainApp, SIGNAL(appInitialized()), this, SLOT(initExportObjectsMenus())); + connect(mainApp, SIGNAL(appInitialized()), this, SLOT(initFollowStreamMenus())); + + connect(mainApp, SIGNAL(profileChanging()), this, SLOT(saveWindowGeometry())); + connect(mainApp, SIGNAL(preferencesChanged()), this, SLOT(layoutPanes())); + connect(mainApp, SIGNAL(preferencesChanged()), this, SLOT(layoutToolbars())); + connect(mainApp, SIGNAL(preferencesChanged()), this, SLOT(updatePreferenceActions())); + connect(mainApp, SIGNAL(preferencesChanged()), this, SLOT(zoomText())); + connect(mainApp, SIGNAL(preferencesChanged()), this, SLOT(setTitlebarForCaptureFile())); + + connect(mainApp, SIGNAL(updateRecentCaptureStatus(const QString &, qint64, bool)), this, SLOT(updateRecentCaptures())); + updateRecentCaptures(); + +#if defined(HAVE_SOFTWARE_UPDATE) && defined(Q_OS_WIN) + connect(mainApp, SIGNAL(softwareUpdateRequested()), this, SLOT(softwareUpdateRequested()), + Qt::BlockingQueuedConnection); +#endif + + df_combo_box_ = new DisplayFilterCombo(this); + + funnel_statistics_ = new FunnelStatistics(this, capture_file_); + connect(df_combo_box_, &QComboBox::editTextChanged, funnel_statistics_, &FunnelStatistics::displayFilterTextChanged); + connect(funnel_statistics_, &FunnelStatistics::setDisplayFilter, this, &WiresharkMainWindow::setDisplayFilter); + connect(funnel_statistics_, SIGNAL(openCaptureFile(QString, QString)), + this, SLOT(openCaptureFile(QString, QString))); + + file_set_dialog_ = new FileSetDialog(this); + connect(file_set_dialog_, SIGNAL(fileSetOpenCaptureFile(QString)), + this, SLOT(openCaptureFile(QString))); + + initMainToolbarIcons(); + + main_ui_->displayFilterToolBar->insertWidget(main_ui_->actionNewDisplayFilterExpression, df_combo_box_); + + // Make sure filter expressions overflow into a menu instead of a + // larger toolbar. We do this by adding them to a child toolbar. + // https://bugreports.qt.io/browse/QTBUG-2472 + FilterExpressionToolBar *filter_expression_toolbar_ = new FilterExpressionToolBar(this); + connect(filter_expression_toolbar_, &FilterExpressionToolBar::filterPreferences, this, &WiresharkMainWindow::onFilterPreferences); + connect(filter_expression_toolbar_, &FilterExpressionToolBar::filterSelected, this, &WiresharkMainWindow::onFilterSelected); + connect(filter_expression_toolbar_, &FilterExpressionToolBar::filterEdit, this, &WiresharkMainWindow::onFilterEdit); + + main_ui_->displayFilterToolBar->addWidget(filter_expression_toolbar_); + +#if defined(HAVE_LIBNL) && defined(HAVE_NL80211) + connect(wireless_frame_, SIGNAL(showWirelessPreferences(QString)), + this, SLOT(showPreferencesDialog(QString))); +#endif + + main_ui_->goToFrame->hide(); + connect(main_ui_->goToFrame, SIGNAL(visibilityChanged(bool)), + main_ui_->actionGoGoToPacket, SLOT(setChecked(bool))); + + // XXX For some reason the cursor is drawn funny with an input mask set + // https://bugreports.qt-project.org/browse/QTBUG-7174 + + main_ui_->searchFrame->hide(); + connect(main_ui_->searchFrame, SIGNAL(visibilityChanged(bool)), + main_ui_->actionEditFindPacket, SLOT(setChecked(bool))); + + main_ui_->addressEditorFrame->hide(); + main_ui_->columnEditorFrame->hide(); + main_ui_->preferenceEditorFrame->hide(); + main_ui_->filterExpressionFrame->hide(); + +#ifndef HAVE_LIBPCAP + main_ui_->menuCapture->setEnabled(false); + main_ui_->actionCaptureStart->setEnabled(false); + main_ui_->actionCaptureStop->setEnabled(false); + main_ui_->actionCaptureRestart->setEnabled(false); + main_ui_->actionCaptureOptions->setEnabled(false); + main_ui_->actionCaptureRefreshInterfaces->setEnabled(false); +#endif + + // Set OS specific shortcuts for fullscreen mode +#if defined(Q_OS_MAC) + main_ui_->actionViewFullScreen->setShortcut(QKeySequence::FullScreen); +#else + main_ui_->actionViewFullScreen->setShortcut(QKeySequence(Qt::Key_F11)); +#endif + +#if defined(Q_OS_MAC) + + main_ui_->goToPacketLabel->setAttribute(Qt::WA_MacSmallSize, true); + main_ui_->goToLineEdit->setAttribute(Qt::WA_MacSmallSize, true); + main_ui_->goToGo->setAttribute(Qt::WA_MacSmallSize, true); + main_ui_->goToCancel->setAttribute(Qt::WA_MacSmallSize, true); + + main_ui_->actionEditPreferences->setMenuRole(QAction::PreferencesRole); + +#endif // Q_OS_MAC + + connect(main_ui_->goToGo, &QPushButton::pressed, this, &WiresharkMainWindow::goToGoClicked); + connect(main_ui_->goToCancel, &QPushButton::pressed, this, &WiresharkMainWindow::goToCancelClicked); + +// A billion-1 is equivalent to the inputMask 900000000 previously used +// Avoid QValidator::Intermediate values by using a top value of all 9's +#define MAX_GOTO_LINE 999999999 + +QIntValidator *goToLineQiv = new QIntValidator(0,MAX_GOTO_LINE,this); +main_ui_->goToLineEdit->setValidator(goToLineQiv); + +#ifdef HAVE_SOFTWARE_UPDATE + QAction *update_sep = main_ui_->menuHelp->insertSeparator(main_ui_->actionHelpAbout); + main_ui_->menuHelp->insertAction(update_sep, update_action_); + connect(update_action_, SIGNAL(triggered()), this, SLOT(checkForUpdates())); +#endif + master_split_.setObjectName("splitterMaster"); + extra_split_.setObjectName("splitterExtra"); + master_split_.setChildrenCollapsible(false); + extra_split_.setChildrenCollapsible(false); + main_ui_->mainStack->addWidget(&master_split_); + + empty_pane_.setObjectName("emptyPane"); + empty_pane_.setVisible(false); + + packet_list_ = new PacketList(&master_split_); + main_ui_->wirelessTimelineWidget->setPacketList(packet_list_); + connect(packet_list_, SIGNAL(framesSelected(QList<int>)), this, SLOT(setMenusForSelectedPacket())); + connect(packet_list_, SIGNAL(framesSelected(QList<int>)), this, SIGNAL(framesSelected(QList<int>))); + + QAction *action = main_ui_->menuPacketComment->addAction(tr("Add New Comment…")); + connect(action, &QAction::triggered, this, &WiresharkMainWindow::addPacketComment); + action->setShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_C)); + connect(main_ui_->menuPacketComment, SIGNAL(aboutToShow()), this, SLOT(setEditCommentsMenu())); + + proto_tree_ = new ProtoTree(&master_split_); + proto_tree_->installEventFilter(this); + + packet_list_->setProtoTree(proto_tree_); + packet_list_->installEventFilter(this); + + packet_diagram_ = new PacketDiagram(&master_split_); + + main_stack_ = main_ui_->mainStack; + welcome_page_ = main_ui_->welcomePage; + main_status_bar_ = main_ui_->statusBar; + + connect(proto_tree_, &ProtoTree::fieldSelected, + this, &WiresharkMainWindow::fieldSelected); + connect(packet_list_, &PacketList::fieldSelected, + this, &WiresharkMainWindow::fieldSelected); + connect(this, &WiresharkMainWindow::fieldSelected, + this, &WiresharkMainWindow::setMenusForSelectedTreeRow); + connect(this, &WiresharkMainWindow::fieldSelected, + main_ui_->statusBar, &MainStatusBar::selectedFieldChanged); + + connect(this, &WiresharkMainWindow::fieldHighlight, + main_ui_->statusBar, &MainStatusBar::highlightedFieldChanged); + connect(mainApp, &WiresharkApplication::captureActive, + this, &WiresharkMainWindow::captureActive); + + byte_view_tab_ = new ByteViewTab(&master_split_); + + // Packet list and proto tree must exist before these are called. + setMenusForSelectedPacket(); + setMenusForSelectedTreeRow(); + + initShowHideMainWidgets(); + initTimeDisplayFormatMenu(); + initTimePrecisionFormatMenu(); + initFreezeActions(); + updatePreferenceActions(); + updateRecentActions(); + setForCaptureInProgress(false); + + setTabOrder(df_combo_box_->lineEdit(), packet_list_); + setTabOrder(packet_list_, proto_tree_); + + connect(&capture_file_, SIGNAL(captureEvent(CaptureEvent)), + this, SLOT(captureEventHandler(CaptureEvent))); + connect(&capture_file_, SIGNAL(captureEvent(CaptureEvent)), + mainApp, SLOT(captureEventHandler(CaptureEvent))); + connect(&capture_file_, SIGNAL(captureEvent(CaptureEvent)), + main_ui_->statusBar, SLOT(captureEventHandler(CaptureEvent))); + + connect(mainApp, SIGNAL(freezePacketList(bool)), + packet_list_, SLOT(freezePacketList(bool))); + connect(mainApp, SIGNAL(columnsChanged()), + packet_list_, SLOT(columnsChanged())); + connect(mainApp, SIGNAL(preferencesChanged()), + packet_list_, SLOT(preferencesChanged())); + connect(mainApp, SIGNAL(recentPreferencesRead()), + this, SLOT(applyRecentPaneGeometry())); + connect(mainApp, SIGNAL(recentPreferencesRead()), + this, SLOT(updateRecentActions())); + connect(mainApp, SIGNAL(packetDissectionChanged()), + this, SLOT(redissectPackets()), Qt::QueuedConnection); + + connect(mainApp, SIGNAL(checkDisplayFilter()), + this, SLOT(checkDisplayFilter())); + connect(mainApp, SIGNAL(fieldsChanged()), + this, SLOT(fieldsChanged())); + connect(mainApp, SIGNAL(reloadLuaPlugins()), + this, SLOT(reloadLuaPlugins())); + + connect(main_ui_->mainStack, SIGNAL(currentChanged(int)), + this, SLOT(mainStackChanged(int))); + + connect(welcome_page_, SIGNAL(startCapture(QStringList)), + this, SLOT(startCapture(QStringList))); + connect(welcome_page_, SIGNAL(recentFileActivated(QString)), + this, SLOT(openCaptureFile(QString))); + + connect(main_ui_->addressEditorFrame, &AddressEditorFrame::redissectPackets, + this, &WiresharkMainWindow::redissectPackets); + connect(main_ui_->addressEditorFrame, &AddressEditorFrame::showNameResolutionPreferences, + this, &WiresharkMainWindow::showPreferencesDialog); + connect(main_ui_->preferenceEditorFrame, &PreferenceEditorFrame::showProtocolPreferences, + this, &WiresharkMainWindow::showPreferencesDialog); + connect(main_ui_->filterExpressionFrame, &FilterExpressionFrame::showPreferencesDialog, + this, &WiresharkMainWindow::showPreferencesDialog); + connect(main_ui_->filterExpressionFrame, &FilterExpressionFrame::filterExpressionsChanged, + filter_expression_toolbar_, &FilterExpressionToolBar::filterExpressionsChanged); + + /* Connect change of capture file */ + connect(this, &WiresharkMainWindow::setCaptureFile, + main_ui_->searchFrame, &SearchFrame::setCaptureFile); + connect(this, &WiresharkMainWindow::setCaptureFile, + main_ui_->statusBar, &MainStatusBar::setCaptureFile); + connect(this, &WiresharkMainWindow::setCaptureFile, + packet_list_, &PacketList::setCaptureFile); + connect(this, &WiresharkMainWindow::setCaptureFile, + proto_tree_, &ProtoTree::setCaptureFile); + + connect(mainApp, SIGNAL(zoomMonospaceFont(QFont)), + packet_list_, SLOT(setMonospaceFont(QFont))); + connect(mainApp, SIGNAL(zoomMonospaceFont(QFont)), + proto_tree_, SLOT(setMonospaceFont(QFont))); + + connectFileMenuActions(); + connectEditMenuActions(); + connectViewMenuActions(); + connectGoMenuActions(); + connectCaptureMenuActions(); + connectAnalyzeMenuActions(); + connectStatisticsMenuActions(); + connectTelephonyMenuActions(); + connectWirelessMenuActions(); + connectToolsMenuActions(); + connectHelpMenuActions(); + + connect(packet_list_, SIGNAL(packetDissectionChanged()), + this, SLOT(redissectPackets())); + connect(packet_list_, SIGNAL(showColumnPreferences(QString)), + this, SLOT(showPreferencesDialog(QString))); + connect(packet_list_, SIGNAL(showProtocolPreferences(QString)), + this, SLOT(showPreferencesDialog(QString))); + connect(packet_list_, SIGNAL(editProtocolPreference(preference*, pref_module*)), + main_ui_->preferenceEditorFrame, SLOT(editPreference(preference*, pref_module*))); + connect(packet_list_, SIGNAL(editColumn(int)), this, SLOT(showColumnEditor(int))); + connect(main_ui_->columnEditorFrame, SIGNAL(columnEdited()), + packet_list_, SLOT(columnsChanged())); + connect(packet_list_, SIGNAL(doubleClicked(QModelIndex)), + this, SLOT(openPacketDialog())); + connect(packet_list_, SIGNAL(packetListScrolled(bool)), + main_ui_->actionGoAutoScroll, SLOT(setChecked(bool))); + + connect(proto_tree_, SIGNAL(openPacketInNewWindow(bool)), + this, SLOT(openPacketDialog(bool))); + connect(proto_tree_, SIGNAL(showProtocolPreferences(QString)), + this, SLOT(showPreferencesDialog(QString))); + connect(proto_tree_, SIGNAL(editProtocolPreference(preference*, pref_module*)), + main_ui_->preferenceEditorFrame, SLOT(editPreference(preference*, pref_module*))); + + connect(main_ui_->statusBar, &MainStatusBar::showExpertInfo, this, [=]() { + statCommandExpertInfo(NULL, NULL); + }); + + connect(main_ui_->statusBar, &MainStatusBar::stopLoading, + &capture_file_, &CaptureFile::stopLoading); + + connect(main_ui_->statusBar, &MainStatusBar::editCaptureComment, + main_ui_->actionStatisticsCaptureFileProperties, &QAction::trigger); + + connect(main_ui_->menuApplyAsFilter, &QMenu::aboutToShow, + this, &WiresharkMainWindow::filterMenuAboutToShow); + connect(main_ui_->menuPrepareAFilter, &QMenu::aboutToShow, + this, &WiresharkMainWindow::filterMenuAboutToShow); + +#ifdef HAVE_LIBPCAP + QTreeWidget *iface_tree = findChild<QTreeWidget *>("interfaceTree"); + if (iface_tree) { + connect(iface_tree, SIGNAL(itemSelectionChanged()), + this, SLOT(interfaceSelectionChanged())); + } + connect(main_ui_->welcomePage, SIGNAL(captureFilterSyntaxChanged(bool)), + this, SLOT(captureFilterSyntaxChanged(bool))); + + connect(this, SIGNAL(showExtcapOptions(QString&, bool)), + this, SLOT(showExtcapOptionsDialog(QString&, bool))); + connect(this->welcome_page_, SIGNAL(showExtcapOptions(QString&, bool)), + this, SLOT(showExtcapOptionsDialog(QString&, bool))); + +#endif // HAVE_LIBPCAP + + /* Create plugin_if hooks */ + plugin_if_register_gui_cb(PLUGIN_IF_FILTER_ACTION_APPLY, plugin_if_mainwindow_apply_filter); + plugin_if_register_gui_cb(PLUGIN_IF_FILTER_ACTION_PREPARE, plugin_if_mainwindow_apply_filter); + plugin_if_register_gui_cb(PLUGIN_IF_PREFERENCE_SAVE, plugin_if_mainwindow_preference); + plugin_if_register_gui_cb(PLUGIN_IF_GOTO_FRAME, plugin_if_mainwindow_gotoframe); +#ifdef HAVE_LIBPCAP + plugin_if_register_gui_cb(PLUGIN_IF_GET_WS_INFO, plugin_if_mainwindow_get_ws_info); +#endif + plugin_if_register_gui_cb(PLUGIN_IF_GET_FRAME_DATA, plugin_if_mainwindow_get_frame_data); + plugin_if_register_gui_cb(PLUGIN_IF_GET_CAPTURE_FILE, plugin_if_mainwindow_get_capture_file); + plugin_if_register_gui_cb(PLUGIN_IF_REMOVE_TOOLBAR, plugin_if_mainwindow_update_toolbars); + + /* Register Interface Toolbar callbacks */ + iface_toolbar_register_cb(mainwindow_add_toolbar, mainwindow_remove_toolbar); + + /* Show tooltips on menu items that go to websites */ + main_ui_->actionHelpMPWireshark->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_WIRESHARK))); + main_ui_->actionHelpMPWireshark_Filter->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_WIRESHARK_FILTER))); + main_ui_->actionHelpMPCapinfos->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_CAPINFOS))); + main_ui_->actionHelpMPDumpcap->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_DUMPCAP))); + main_ui_->actionHelpMPEditcap->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_EDITCAP))); + main_ui_->actionHelpMPMergecap->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_MERGECAP))); + main_ui_->actionHelpMPRawshark->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_RAWSHARK))); + main_ui_->actionHelpMPReordercap->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_REORDERCAP))); + main_ui_->actionHelpMPText2pcap->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_TEXT2PCAP))); + main_ui_->actionHelpMPTShark->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_TSHARK))); + + main_ui_->actionHelpContents->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_USERGUIDE))); + main_ui_->actionHelpWebsite->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_HOME))); + main_ui_->actionHelpFAQ->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_FAQ))); + main_ui_->actionHelpAsk->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_ASK))); + main_ui_->actionHelpDownloads->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_DOWNLOAD))); + main_ui_->actionHelpWiki->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_WIKI))); + main_ui_->actionHelpSampleCaptures->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_SAMPLE_CAPTURES))); + + showWelcome(); +} + +WiresharkMainWindow::~WiresharkMainWindow() +{ + disconnect(main_ui_->mainStack, 0, 0, 0); + +#ifndef Q_OS_MAC + // Below dialogs inherit GeometryStateDialog + // For reasons described in geometry_state_dialog.h no parent is set when + // instantiating the dialogs and as a resul objects are not automatically + // freed by its parent. Free then here explicitly to avoid leak and numerous + // Valgrind complaints. + delete file_set_dialog_; +#ifdef HAVE_LIBPCAP + delete capture_options_dialog_; +#endif + +#endif + delete main_ui_; +} + +QMenu *WiresharkMainWindow::createPopupMenu() +{ + QMenu *menu = new QMenu(); + menu->addAction(main_ui_->actionViewMainToolbar); + menu->addAction(main_ui_->actionViewFilterToolbar); +#if defined(HAVE_LIBNL) && defined(HAVE_NL80211) + menu->addAction(main_ui_->actionViewWirelessToolbar); +#endif + + if (!main_ui_->menuInterfaceToolbars->actions().isEmpty()) { + QMenu *submenu = menu->addMenu(main_ui_->menuInterfaceToolbars->title()); + foreach(QAction *action, main_ui_->menuInterfaceToolbars->actions()) { + submenu->addAction(action); + } + } + + if (!main_ui_->menuAdditionalToolbars->actions().isEmpty()) { + QMenu *subMenu = menu->addMenu(main_ui_->menuAdditionalToolbars->title()); + foreach(QAction *action, main_ui_->menuAdditionalToolbars->actions()) { + subMenu->addAction(action); + } + } + + menu->addAction(main_ui_->actionViewStatusBar); + + menu->addSeparator(); + menu->addAction(main_ui_->actionViewPacketList); + menu->addAction(main_ui_->actionViewPacketDetails); + menu->addAction(main_ui_->actionViewPacketBytes); + menu->addAction(main_ui_->actionViewPacketDiagram); + return menu; +} + +void WiresharkMainWindow::addInterfaceToolbar(const iface_toolbar *toolbar_entry) +{ + QMenu *menu = main_ui_->menuInterfaceToolbars; + bool visible = g_list_find_custom(recent.interface_toolbars, toolbar_entry->menu_title, (GCompareFunc)strcmp) ? true : false; + + QString title = QString().fromUtf8(toolbar_entry->menu_title); + QAction *action = new QAction(title, menu); + action->setEnabled(true); + action->setCheckable(true); + action->setChecked(visible); + action->setToolTip(tr("Show or hide the toolbar")); + + QAction *before = NULL; + foreach(QAction *action, menu->actions()) { + // Ensure we add the menu entries in sorted order + if (action->text().compare(title, Qt::CaseInsensitive) > 0) { + before = action; + break; + } + } + menu->insertAction(before, action); + + InterfaceToolbar *interface_toolbar = new InterfaceToolbar(this, toolbar_entry); + connect(mainApp, SIGNAL(appInitialized()), interface_toolbar, SLOT(interfaceListChanged())); + connect(mainApp, SIGNAL(localInterfaceListChanged()), interface_toolbar, SLOT(interfaceListChanged())); + + QToolBar *toolbar = new QToolBar(this); + toolbar->addWidget(interface_toolbar); + toolbar->setMovable(false); + toolbar->setVisible(visible); + + action->setData(QVariant::fromValue(toolbar)); + + addToolBar(Qt::TopToolBarArea, toolbar); + insertToolBarBreak(toolbar); + + if (show_hide_actions_) { + show_hide_actions_->addAction(action); + } + + menu->menuAction()->setVisible(true); +} + +void WiresharkMainWindow::removeInterfaceToolbar(const gchar *menu_title) +{ + QMenu *menu = main_ui_->menuInterfaceToolbars; + QAction *action = NULL; + QMap<QAction *, QWidget *>::iterator i; + + QString title = QString().fromUtf8(menu_title); + foreach(action, menu->actions()) { + if (title.compare(action->text()) == 0) { + break; + } + } + + if (action) { + if (show_hide_actions_) { + show_hide_actions_->removeAction(action); + } + menu->removeAction(action); + + QToolBar *toolbar = action->data().value<QToolBar *>(); + removeToolBar(toolbar); + + delete action; + delete toolbar; + } + + menu->menuAction()->setVisible(!menu->actions().isEmpty()); +} + +void WiresharkMainWindow::updateStyleSheet() +{ +#ifdef Q_OS_MAC + // TODO: The event type QEvent::ApplicationPaletteChange is not sent to all child widgets. + // Workaround this by doing it manually for all AccordionFrame. + main_ui_->addressEditorFrame->updateStyleSheet(); + main_ui_->columnEditorFrame->updateStyleSheet(); + main_ui_->filterExpressionFrame->updateStyleSheet(); + main_ui_->goToFrame->updateStyleSheet(); + main_ui_->preferenceEditorFrame->updateStyleSheet(); + main_ui_->searchFrame->updateStyleSheet(); + + df_combo_box_->updateStyleSheet(); + welcome_page_->updateStyleSheets(); +#endif +} + +bool WiresharkMainWindow::eventFilter(QObject *obj, QEvent *event) { + + // The user typed some text. Start filling in a filter. + // We may need to be more choosy here. We just need to catch events for the packet list, + // proto tree, and main welcome widgets. + if (event->type() == QEvent::KeyPress) { + QKeyEvent *kevt = static_cast<QKeyEvent *>(event); + if (kevt->text().length() > 0 && kevt->text()[0].isPrint() && + !(kevt->modifiers() & Qt::ControlModifier)) { + df_combo_box_->lineEdit()->insert(kevt->text()); + df_combo_box_->lineEdit()->setFocus(); + return true; + } + } + + return QMainWindow::eventFilter(obj, event); +} + +bool WiresharkMainWindow::event(QEvent *event) +{ + switch (event->type()) { + case QEvent::ApplicationPaletteChange: + initMainToolbarIcons(); + updateStyleSheet(); + break; + default: + break; + + } + return QMainWindow::event(event); +} + +void WiresharkMainWindow::keyPressEvent(QKeyEvent *event) { + + // Explicitly focus on the display filter combo. + if (event->modifiers() & Qt::ControlModifier && event->key() == Qt::Key_Slash) { + df_combo_box_->setFocus(Qt::ShortcutFocusReason); + return; + } + + if (mainApp->focusWidget() == main_ui_->goToLineEdit) { + if (event->modifiers() == Qt::NoModifier) { + if (event->key() == Qt::Key_Escape) { + goToCancelClicked(); + } else if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) { + goToGoClicked(); + } + } + return; // goToLineEdit didn't want it and we don't either. + } + + // Move up & down the packet list. + if (event->key() == Qt::Key_F7) { + packet_list_->goPreviousPacket(); + } else if (event->key() == Qt::Key_F8) { + packet_list_->goNextPacket(); + } + + // Move along, citizen. + QMainWindow::keyPressEvent(event); +} + +void WiresharkMainWindow::closeEvent(QCloseEvent *event) { + if (main_ui_->actionCaptureStop->isEnabled()) { + // Capture is running, we should stop it before close and ignore the event + stopCapture(); + event->ignore(); + return; + } + + saveWindowGeometry(); + + /* If we're in the middle of stopping a capture, don't do anything; + the user can try deleting the window after the capture stops. */ + if (capture_stopping_) { + event->ignore(); + return; + } + + QString before_what(tr(" before quitting")); + if (!testCaptureFileClose(before_what, Quit)) { + event->ignore(); + return; + } + +#ifdef HAVE_LIBPCAP + if (capture_options_dialog_) capture_options_dialog_->close(); +#endif + // Make sure we kill any open dumpcap processes. + delete welcome_page_; + + // One of the many places we assume one main window. + if (!mainApp->isInitialized()) { + // If we're still initializing, QCoreApplication::quit() won't + // exit properly because we are not in the event loop. This + // means that the application won't clean up after itself. We + // might want to call mainApp->processEvents() during startup + // instead so that we can do a normal exit here. + exit(0); + } + mainApp->quit(); + // When the main loop is not yet running (i.e. when openCaptureFile is + // executing in main.cpp), the above quit action has no effect. + // Schedule a quit action for the next execution of the main loop. + QMetaObject::invokeMethod(mainApp, "quit", Qt::QueuedConnection); +} + +// XXX On windows the drag description is "Copy". It should be "Open" or +// "Merge" as appropriate. It looks like we need access to IDataObject in +// order to set DROPDESCRIPTION. +void WiresharkMainWindow::dragEnterEvent(QDragEnterEvent *event) +{ + if (!event->mimeData()->hasUrls()) + { + event->ignore(); + return; + } + + if (!main_ui_->actionFileOpen->isEnabled()) { + // We could alternatively call setAcceptDrops(!capture_in_progress) + // in setMenusForCaptureInProgress but that wouldn't provide feedback. + + mainApp->pushStatus(WiresharkApplication::TemporaryStatus, tr("Unable to drop files during capture.")); + event->setDropAction(Qt::IgnoreAction); + event->ignore(); + return; + } + + bool have_files = false; + foreach(QUrl drag_url, event->mimeData()->urls()) { + if (!drag_url.toLocalFile().isEmpty()) { + have_files = true; + break; + } + } + + if (have_files) { + event->acceptProposedAction(); + } +} + +void WiresharkMainWindow::dropEvent(QDropEvent *event) +{ + if (!event->mimeData()->hasUrls()) + { + event->ignore(); + return; + } + + QList<QByteArray> local_files; + int max_dropped_files = 100; // Arbitrary + + foreach(QUrl drop_url, event->mimeData()->urls()) { + QString drop_file = drop_url.toLocalFile(); + if (!drop_file.isEmpty()) { + local_files << drop_file.toUtf8(); + if (local_files.size() >= max_dropped_files) { + break; + } + } + } + + event->acceptProposedAction(); + + if (local_files.size() < 1) { + event->ignore(); + return; + } + + event->accept(); + + if (local_files.size() == 1) { + openCaptureFile(local_files.at(0)); + return; + } + + const char **in_filenames = g_new(const char *, local_files.size()); + char *tmpname = NULL; + + for (int i = 0; i < local_files.size(); i++) { + in_filenames[i] = local_files.at(i).constData(); + } + + /* merge the files in chronological order */ + if (cf_merge_files_to_tempfile(this, global_capture_opts.temp_dir, &tmpname, static_cast<int>(local_files.size()), + in_filenames, + wtap_pcapng_file_type_subtype(), + FALSE) == CF_OK) { + /* Merge succeeded; close the currently-open file and try + to open the merged capture file. */ + openCaptureFile(tmpname, QString(), WTAP_TYPE_AUTO, TRUE); + } + + g_free(tmpname); + g_free(in_filenames); +} + +// Apply recent settings to the main window geometry. +// We haven't loaded the preferences at this point so we assume that the +// position and size preference are enabled. +// Note we might end up with unexpected screen geometries if the user +// unplugs or plugs in a monitor: +// https://bugreports.qt.io/browse/QTBUG-44213 +void WiresharkMainWindow::loadWindowGeometry() +{ + int min_sensible_dimension = 200; + +#ifndef Q_OS_MAC + if (recent.gui_geometry_main_maximized) { + setWindowState(Qt::WindowMaximized); + } else +#endif + { + QRect recent_geom(recent.gui_geometry_main_x, recent.gui_geometry_main_y, + recent.gui_geometry_main_width, recent.gui_geometry_main_height); + if (!rect_on_screen(recent_geom)) { + // We're not visible on any screens. See if we can move onscreen + // without resizing. + recent_geom.moveTo(50, 50); // recent.c defaults to 20. + } + + if (!rect_on_screen(recent_geom)) { + // Give up and use the default geometry. + return; + } + +// if (prefs.gui_geometry_save_position) { + move(recent_geom.topLeft()); +// } + + if (// prefs.gui_geometry_save_size && + recent_geom.width() > min_sensible_dimension && + recent_geom.height() > min_sensible_dimension) { + resize(recent_geom.size()); + } + } +} + +void WiresharkMainWindow::saveWindowGeometry() +{ + if (prefs.gui_geometry_save_position) { + recent.gui_geometry_main_x = pos().x(); + recent.gui_geometry_main_y = pos().y(); + } + + if (prefs.gui_geometry_save_size) { + recent.gui_geometry_main_width = size().width(); + recent.gui_geometry_main_height = size().height(); + } + + if (prefs.gui_geometry_save_maximized) { + // On macOS this is false when it shouldn't be + recent.gui_geometry_main_maximized = isMaximized(); + } + + if (master_split_.sizes().length() > 0) { + recent.gui_geometry_main_upper_pane = master_split_.sizes()[0]; + } + + if (master_split_.sizes().length() > 2) { + recent.gui_geometry_main_lower_pane = master_split_.sizes()[1]; + } else if (extra_split_.sizes().length() > 0) { + recent.gui_geometry_main_lower_pane = extra_split_.sizes()[0]; + } +} + +// Our event loop becomes nested whenever we call update_progress_dlg, which +// includes several places in file.c. The GTK+ UI stays out of trouble by +// showing a modal progress dialog. We attempt to do the equivalent below by +// disabling parts of the main window. At a minumum the ProgressFrame in the +// main status bar must remain accessible. +// +// We might want to do this any time the main status bar progress frame is +// shown and hidden. +void WiresharkMainWindow::freeze() +{ + freeze_focus_ = mainApp->focusWidget(); + + // XXX Alternatively we could just disable and enable the main menu. + for (int i = 0; i < freeze_actions_.size(); i++) { + QAction *action = freeze_actions_[i].first; + freeze_actions_[i].second = action->isEnabled(); + action->setEnabled(false); + } + main_ui_->centralWidget->setEnabled(false); +} + +void WiresharkMainWindow::thaw() +{ + main_ui_->centralWidget->setEnabled(true); + for (int i = 0; i < freeze_actions_.size(); i++) { + freeze_actions_[i].first->setEnabled(freeze_actions_[i].second); + } + + if (freeze_focus_) freeze_focus_->setFocus(); + freeze_focus_ = NULL; +} + +void WiresharkMainWindow::mergeCaptureFile() +{ + QString file_name = ""; + QString read_filter = ""; + dfilter_t *rfcode = NULL; + int err; + + if (!capture_file_.capFile()) + return; + + if (prefs.gui_ask_unsaved) { + if (cf_has_unsaved_data(capture_file_.capFile())) { + QMessageBox msg_dialog; + gchar *display_basename; + int response; + + msg_dialog.setIcon(QMessageBox::Question); + /* This file has unsaved data; ask the user whether to save + the capture. */ + if (capture_file_.capFile()->is_tempfile) { + msg_dialog.setText(tr("Save packets before merging?")); + msg_dialog.setInformativeText(tr("A temporary capture file can't be merged.")); + } else { + /* + * Format the message. + */ + display_basename = g_filename_display_basename(capture_file_.capFile()->filename); + msg_dialog.setText(QString(tr("Save changes in \"%1\" before merging?")).arg(display_basename)); + g_free(display_basename); + msg_dialog.setInformativeText(tr("Changes must be saved before the files can be merged.")); + } + + msg_dialog.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel); + msg_dialog.setDefaultButton(QMessageBox::Save); + + response = msg_dialog.exec(); + + switch (response) { + + case QMessageBox::Save: + /* Save the file but don't close it */ + saveCaptureFile(capture_file_.capFile(), false); + break; + + case QMessageBox::Cancel: + default: + /* Don't do the merge. */ + return; + } + } + } + + for (;;) { + CaptureFileDialog merge_dlg(this, capture_file_.capFile()); + int file_type; + cf_status_t merge_status; + char *in_filenames[2]; + char *tmpname; + + if (merge_dlg.merge(file_name, read_filter)) { + df_error_t *df_err = NULL; + + if (!dfilter_compile(qUtf8Printable(read_filter), &rfcode, &df_err)) { + /* Not valid. Tell the user, and go back and run the file + selection box again once they dismiss the alert. */ + // Similar to commandline_info.jfilter section in main(). + QMessageBox::warning(this, tr("Invalid Read Filter"), + QString(tr("The filter expression %1 isn't a valid read filter. (%2).").arg(read_filter, df_err->msg)), + QMessageBox::Ok); + df_error_free(&df_err); + continue; + } + } else { + return; + } + + file_type = capture_file_.capFile()->cd_t; + + /* Try to merge or append the two files */ + if (merge_dlg.mergeType() == 0) { + /* chronological order */ + in_filenames[0] = g_strdup(capture_file_.capFile()->filename); + in_filenames[1] = qstring_strdup(file_name); + merge_status = cf_merge_files_to_tempfile(this, global_capture_opts.temp_dir, &tmpname, 2, in_filenames, file_type, FALSE); + } else if (merge_dlg.mergeType() <= 0) { + /* prepend file */ + in_filenames[0] = qstring_strdup(file_name); + in_filenames[1] = g_strdup(capture_file_.capFile()->filename); + merge_status = cf_merge_files_to_tempfile(this, global_capture_opts.temp_dir, &tmpname, 2, in_filenames, file_type, TRUE); + } else { + /* append file */ + in_filenames[0] = g_strdup(capture_file_.capFile()->filename); + in_filenames[1] = qstring_strdup(file_name); + merge_status = cf_merge_files_to_tempfile(this, global_capture_opts.temp_dir, &tmpname, 2, in_filenames, file_type, TRUE); + } + + g_free(in_filenames[0]); + g_free(in_filenames[1]); + + if (merge_status != CF_OK) { + dfilter_free(rfcode); + g_free(tmpname); + continue; + } + + cf_close(capture_file_.capFile()); + + /* Try to open the merged capture file. */ + CaptureFile::globalCapFile()->window = this; + if (cf_open(CaptureFile::globalCapFile(), tmpname, WTAP_TYPE_AUTO, TRUE /* temporary file */, &err) != CF_OK) { + /* We couldn't open it; fail. */ + CaptureFile::globalCapFile()->window = NULL; + dfilter_free(rfcode); + g_free(tmpname); + return; + } + + /* Attach the new read filter to "cf" ("cf_open()" succeeded, so + it closed the previous capture file, and thus destroyed any + previous read filter attached to "cf"). */ + cf_set_rfcode(CaptureFile::globalCapFile(), rfcode); + + switch (cf_read(CaptureFile::globalCapFile(), /*reloading=*/FALSE)) { + + case CF_READ_OK: + case CF_READ_ERROR: + /* Just because we got an error, that doesn't mean we were unable + to read any of the file; we handle what we could get from the + file. */ + break; + + case CF_READ_ABORTED: + /* The user bailed out of re-reading the capture file; the + capture file has been closed - just free the capture file name + string and return (without changing the last containing + directory). */ + g_free(tmpname); + return; + } + + /* Save the name of the containing directory specified in the path name. */ + mainApp->setLastOpenDirFromFilename(tmpname); + g_free(tmpname); + main_ui_->statusBar->showExpert(); + return; + } + +} + +void WiresharkMainWindow::importCaptureFile() { + ImportTextDialog import_dlg; + + QString before_what(tr(" before importing a capture")); + if (!testCaptureFileClose(before_what)) + return; + + import_dlg.exec(); + + if (import_dlg.result() != QDialog::Accepted) { + showWelcome(); + return; + } + + openCaptureFile(import_dlg.capfileName()); +} + +bool WiresharkMainWindow::saveCaptureFile(capture_file *cf, bool dont_reopen) { + QString file_name; + gboolean discard_comments; + + if (cf->is_tempfile) { + /* This is a temporary capture file, so saving it means saving + it to a permanent file. Prompt the user for a location + to which to save it. Don't require that the file format + support comments - if it's a temporary capture file, it's + probably pcapng, which supports comments and, if it's + not pcapng, let the user decide what they want to do + if they've added comments. */ + return saveAsCaptureFile(cf, FALSE, dont_reopen); + } else { + if (cf->unsaved_changes) { + cf_write_status_t status; + + /* This is not a temporary capture file, but it has unsaved + changes, so saving it means doing a "safe save" on top + of the existing file, in the same format - no UI needed + unless the file has comments and the file's format doesn't + support them. + + If the file has comments, does the file's format support them? + If not, ask the user whether they want to discard the comments + or choose a different format. */ + switch (CaptureFileDialog::checkSaveAsWithComments(this, cf, cf->cd_t)) { + + case SAVE: + /* The file can be saved in the specified format as is; + just drive on and save in the format they selected. */ + discard_comments = FALSE; + break; + + case SAVE_WITHOUT_COMMENTS: + /* The file can't be saved in the specified format as is, + but it can be saved without the comments, and the user + said "OK, discard the comments", so save it in the + format they specified without the comments. */ + discard_comments = TRUE; + break; + + case SAVE_IN_ANOTHER_FORMAT: + /* There are file formats in which we can save this that + support comments, and the user said not to delete the + comments. Do a "Save As" so the user can select + one of those formats and choose a file name. */ + return saveAsCaptureFile(cf, TRUE, dont_reopen); + + case CANCELLED: + /* The user said "forget it". Just return. */ + return false; + + default: + /* Squelch warnings that discard_comments is being used + uninitialized. */ + ws_assert_not_reached(); + return false; + } + + /* XXX - cf->filename might get freed out from under us, because + the code path through which cf_save_records() goes currently + closes the current file and then opens and reloads the saved file, + so make a copy and free it later. */ + file_name = cf->filename; + status = cf_save_records(cf, qUtf8Printable(file_name), cf->cd_t, cf->compression_type, + discard_comments, dont_reopen); + switch (status) { + + case CF_WRITE_OK: + /* The save succeeded; we're done. + If we discarded comments, redraw the packet list to reflect + any packets that no longer have comments. If we had unsaved + changes, redraw the packet list, because saving a time + shift zeroes out the frame.offset_shift field. + If we had a color filter based on frame data, recolor. */ + /* XXX: If there is a filter based on those, we want to force + a rescan with the current filter (we don't actually + need to redissect.) + */ + if (discard_comments || cf->unsaved_changes) { + if (color_filters_use_proto(proto_get_id_by_filter_name("frame"))) { + packet_list_->recolorPackets(); + } else { + packet_list_->redrawVisiblePackets(); + } + } + + cf->unsaved_changes = false; //we just saved so we signal that we have no unsaved changes + updateForUnsavedChanges(); // we update the title bar to remove the * + break; + + case CF_WRITE_ERROR: + /* The write failed. + XXX - OK, what do we do now? Let them try a + "Save As", in case they want to try to save to a + different directory or file system? */ + break; + + case CF_WRITE_ABORTED: + /* The write was aborted; just drive on. */ + return false; + } + } + /* Otherwise just do nothing. */ + } + + return true; +} + +bool WiresharkMainWindow::saveAsCaptureFile(capture_file *cf, bool must_support_comments, bool dont_reopen) { + QString file_name = ""; + int file_type; + wtap_compression_type compression_type; + cf_write_status_t status; + gchar *dirname; + gboolean discard_comments = FALSE; + + if (!cf) { + return false; + } + + for (;;) { + CaptureFileDialog save_as_dlg(this, cf); + + /* If the file has comments, does the format the user selected + support them? If not, ask the user whether they want to + discard the comments or choose a different format. */ + switch (save_as_dlg.saveAs(file_name, must_support_comments)) { + + case SAVE: + /* The file can be saved in the specified format as is; + just drive on and save in the format they selected. */ + discard_comments = FALSE; + break; + + case SAVE_WITHOUT_COMMENTS: + /* The file can't be saved in the specified format as is, + but it can be saved without the comments, and the user + said "OK, discard the comments", so save it in the + format they specified without the comments. */ + discard_comments = TRUE; + break; + + case SAVE_IN_ANOTHER_FORMAT: + /* There are file formats in which we can save this that + support comments, and the user said not to delete the + comments. The combo box of file formats has had the + formats that don't support comments trimmed from it, + so run the dialog again, to let the user decide + whether to save in one of those formats or give up. */ + must_support_comments = TRUE; + continue; + + case CANCELLED: + /* The user said "forget it". Just get rid of the dialog box + and return. */ + return false; + } + file_type = save_as_dlg.selectedFileType(); + if (file_type == WTAP_FILE_TYPE_SUBTYPE_UNKNOWN) { + /* This "should not happen". */ + QMessageBox msg_dialog; + + msg_dialog.setIcon(QMessageBox::Critical); + msg_dialog.setText(tr("Unknown file type returned by merge dialog.")); + msg_dialog.setInformativeText(tr("Please report this as a Wireshark issue at https://gitlab.com/wireshark/wireshark/-/issues.")); + msg_dialog.exec(); + return false; + } + compression_type = save_as_dlg.compressionType(); + +#ifdef Q_OS_WIN + // the Windows dialog does not fixup extensions, do it manually here. + fileAddExtension(file_name, file_type, compression_type); +#endif // Q_OS_WIN + +//#ifndef _WIN32 +// /* If the file exists and it's user-immutable or not writable, +// ask the user whether they want to override that. */ +// if (!file_target_unwritable_ui(top_level, qUtf8Printable(file_name))) { +// /* They don't. Let them try another file name or cancel. */ +// continue; +// } +//#endif + + /* Attempt to save the file */ + status = cf_save_records(cf, qUtf8Printable(file_name), file_type, compression_type, + discard_comments, dont_reopen); + switch (status) { + + case CF_WRITE_OK: + /* The save succeeded; we're done. */ + /* Save the directory name for future file dialogs. */ + dirname = qstring_strdup(file_name); /* Overwrites cf_name */ + set_last_open_dir(get_dirname(dirname)); + g_free(dirname); + /* The save succeeded; we're done. + If we discarded comments, redraw the packet list to reflect + any packets that no longer have comments. If we had unsaved + changes, redraw the packet list, because saving a time + shift zeroes out the frame.offset_shift field. + If we had a color filter based on frame data, recolor. */ + /* XXX: If there is a filter based on those, we want to force + a rescan with the current filter (we don't actually + need to redissect.) + */ + if (discard_comments || cf->unsaved_changes) { + if (color_filters_use_proto(proto_get_id_by_filter_name("frame"))) { + packet_list_->recolorPackets(); + } else { + packet_list_->redrawVisiblePackets(); + } + } + + cf->unsaved_changes = false; //we just saved so we signal that we have no unsaved changes + updateForUnsavedChanges(); // we update the title bar to remove the * + /* Add this filename to the list of recent files in the "Recent Files" submenu */ + add_menu_recent_capture_file(qUtf8Printable(file_name)); + return true; + + case CF_WRITE_ERROR: + /* The save failed; let the user try again. */ + continue; + + case CF_WRITE_ABORTED: + /* The user aborted the save; just return. */ + return false; + } + } + return true; +} + +void WiresharkMainWindow::exportSelectedPackets() { + QString file_name = ""; + int file_type; + wtap_compression_type compression_type; + packet_range_t range; + cf_write_status_t status; + gchar *dirname; + bool discard_comments = false; + + if (!capture_file_.capFile()) + return; + + /* Init the packet range */ + packet_range_init(&range, capture_file_.capFile()); + range.process_filtered = TRUE; + range.include_dependents = TRUE; + + QList<int> rows = packet_list_->selectedRows(true); + + QStringList entries; + foreach (int row, rows) + entries << QString::number(row); + QString selRange = entries.join(","); + + for (;;) { + CaptureFileDialog esp_dlg(this, capture_file_.capFile()); + + /* If the file has comments, does the format the user selected + support them? If not, ask the user whether they want to + discard the comments or choose a different format. */ + switch (esp_dlg.exportSelectedPackets(file_name, &range, selRange)) { + + case SAVE: + /* The file can be saved in the specified format as is; + just drive on and save in the format they selected. */ + discard_comments = FALSE; + break; + + case SAVE_WITHOUT_COMMENTS: + /* The file can't be saved in the specified format as is, + but it can be saved without the comments, and the user + said "OK, discard the comments", so save it in the + format they specified without the comments. */ + discard_comments = TRUE; + break; + + case SAVE_IN_ANOTHER_FORMAT: + /* There are file formats in which we can save this that + support comments, and the user said not to delete the + comments. The combo box of file formats has had the + formats that don't support comments trimmed from it, + so run the dialog again, to let the user decide + whether to save in one of those formats or give up. */ + continue; + + case CANCELLED: + /* The user said "forget it". Just get rid of the dialog box + and return. */ + goto cleanup; + } + + /* + * Check that we're not going to save on top of the current + * capture file. + * We do it here so we catch all cases ... + * Unfortunately, the file requester gives us an absolute file + * name and the read file name may be relative (if supplied on + * the command line). From Joerg Mayer. + */ + if (files_identical(capture_file_.capFile()->filename, qUtf8Printable(file_name))) { + QMessageBox msg_box; + gchar *display_basename = g_filename_display_basename(qUtf8Printable(file_name)); + + msg_box.setIcon(QMessageBox::Critical); + msg_box.setText(QString(tr("Unable to export to \"%1\".").arg(display_basename))); + msg_box.setInformativeText(tr("You cannot export packets to the current capture file.")); + msg_box.setStandardButtons(QMessageBox::Ok); + msg_box.setDefaultButton(QMessageBox::Ok); + msg_box.exec(); + g_free(display_basename); + continue; + } + + file_type = esp_dlg.selectedFileType(); + if (file_type == WTAP_FILE_TYPE_SUBTYPE_UNKNOWN) { + /* This "should not happen". */ + QMessageBox msg_box; + + msg_box.setIcon(QMessageBox::Critical); + msg_box.setText(tr("Unknown file type returned by export dialog.")); + msg_box.setInformativeText(tr("Please report this as a Wireshark issue at https://gitlab.com/wireshark/wireshark/-/issues.")); + msg_box.exec(); + goto cleanup; + } + compression_type = esp_dlg.compressionType(); +#ifdef Q_OS_WIN + // the Windows dialog does not fixup extensions, do it manually here. + fileAddExtension(file_name, file_type, compression_type); +#endif // Q_OS_WIN + +//#ifndef _WIN32 +// /* If the file exists and it's user-immutable or not writable, +// ask the user whether they want to override that. */ +// if (!file_target_unwritable_ui(top_level, qUtf8Printable(file_name))) { +// /* They don't. Let them try another file name or cancel. */ +// continue; +// } +//#endif + + /* Attempt to save the file */ + status = cf_export_specified_packets(capture_file_.capFile(), qUtf8Printable(file_name), &range, file_type, compression_type); + switch (status) { + + case CF_WRITE_OK: + /* The save succeeded; we're done. */ + /* Save the directory name for future file dialogs. */ + dirname = qstring_strdup(file_name); /* Overwrites cf_name */ + set_last_open_dir(get_dirname(dirname)); + g_free(dirname); + /* If we discarded comments, redraw the packet list to reflect + any packets that no longer have comments. */ + /* XXX: Why? We're exporting some packets to a new file but not + changing our current capture file, that shouldn't change the + current packet list. */ + if (discard_comments) + packet_list_->redrawVisiblePackets(); + /* Add this filename to the list of recent files in the "Recent Files" submenu */ + add_menu_recent_capture_file(qUtf8Printable(file_name)); + goto cleanup; + + case CF_WRITE_ERROR: + /* The save failed; let the user try again. */ + continue; + + case CF_WRITE_ABORTED: + /* The user aborted the save; just return. */ + goto cleanup; + } + } + +cleanup: + packet_range_cleanup(&range); +} + +void WiresharkMainWindow::exportDissections(export_type_e export_type) { + capture_file *cf = capture_file_.capFile(); + g_return_if_fail(cf); + + QList<int> rows = packet_list_->selectedRows(true); + + QStringList entries; + foreach (int row, rows) + entries << QString::number(row); + QString selRange = entries.join(","); + + ExportDissectionDialog *ed_dlg = new ExportDissectionDialog(this, cf, export_type, selRange); + ed_dlg->setWindowModality(Qt::ApplicationModal); + ed_dlg->setAttribute(Qt::WA_DeleteOnClose); + ed_dlg->show(); +} + +#ifdef Q_OS_WIN +/* + * Ensure that: + * + * If the file is to be compressed: + * + * if there is a set of extensions used by the file type to be used, + * the file name has one of those extensions followed by the extension + * for the compression type to be used; + * + * otherwise, the file name has the extension for the compression type + * to be used; + * + * otherwise: + * + * if there is a set of extensions used by the file type to be used, + * the file name has one of those extensions. + */ +void WiresharkMainWindow::fileAddExtension(QString &file_name, int file_type, wtap_compression_type compression_type) { + QString file_name_lower; + GSList *extensions_list; + const char *compressed_file_extension; + gboolean add_extension_for_file_type; + + /* Lower-case the file name, so the extension matching is case-insensitive. */ + file_name_lower = file_name.toLower(); + + /* Get a list of all extensions used for this file type; don't + include the ones with compression type extensions, as we + only want to check for the extension for the compression + type we'll be using. */ + extensions_list = wtap_get_file_extensions_list(file_type, FALSE); + + /* Get the extension for the compression type we'll be using; + NULL is returned if the type isn't supported or compression + is not being done. */ + compressed_file_extension = wtap_compression_type_extension(compression_type); + + if (extensions_list != NULL) { + GSList *extension; + + /* This file type has one or more extensions. + Start out assuming we need to add the default one. */ + add_extension_for_file_type = TRUE; + + /* OK, see if the file has one of those extensions, followed + by the appropriate compression type extension if it's to be + compressed. */ + for (extension = extensions_list; extension != NULL; + extension = g_slist_next(extension)) { + QString file_suffix = QString(".") + (char *)extension->data; + if (compressed_file_extension != NULL) + file_suffix += QString(".") + compressed_file_extension; + if (file_name_lower.endsWith(file_suffix)) { + /* + * The file name has one of the extensions for this file + * type, followed by a compression type extension if + * appropriate, so we don't need to add an extension for + * the file type or the compression type. + */ + add_extension_for_file_type = FALSE; + break; + } + } + } else { + /* We have no extensions for this file type. Just check + to see if we need to add an extension for the compressed + file type. + + Start out assuming we do. */ + add_extension_for_file_type = TRUE; + if (compressed_file_extension != NULL) { + QString file_suffix = QString(".") + compressed_file_extension; + if (file_name_lower.endsWith(file_suffix)) { + /* + * The file name has the appropriate compressed file extension, + * so we don't need to add an extension for the compression + * type. + */ + add_extension_for_file_type = FALSE; + } + } + } + + /* + * If we need to add an extension for the file type or compressed + * file type, do so. + */ + if (add_extension_for_file_type) { + if (wtap_default_file_extension(file_type) != NULL) { + /* This file type has a default extension; append it. */ + file_name += QString(".") + wtap_default_file_extension(file_type); + } + if (compression_type != WTAP_UNCOMPRESSED) { + /* + * The file is to be compressed, so append the extension for + * its compression type. + */ + file_name += QString(".") + compressed_file_extension; + } + } +} +#endif // Q_OS_WIN + +bool WiresharkMainWindow::testCaptureFileClose(QString before_what, FileCloseContext context) { + bool capture_in_progress = false; + bool do_close_file = false; + + if (!capture_file_.capFile() || capture_file_.capFile()->state == FILE_CLOSED) + return true; /* Already closed, nothing to do */ + + if (capture_file_.capFile()->read_lock) { + /* + * If the file is being redissected, we cannot stop the capture since + * that would crash and burn "cf_read", so stop early. Ideally all + * callers should be modified to check this condition and act + * accordingly (ignore action or queue it up), so print a warning. + */ + ws_warning("Refusing to close \"%s\" which is being read.", capture_file_.capFile()->filename); + return false; + } + +#ifdef HAVE_LIBPCAP + if (capture_file_.capFile()->state == FILE_READ_IN_PROGRESS || + capture_file_.capFile()->state == FILE_READ_PENDING) { + /* + * FILE_READ_IN_PROGRESS is true if we're reading a capture file + * *or* if we're doing a live capture. From the capture file itself we + * cannot differentiate the cases, so check the current capture session. + * FILE_READ_PENDING is only used for a live capture, but it doesn't + * hurt to check it here. + */ + capture_in_progress = captureSession()->state != CAPTURE_STOPPED; + } +#endif + + if (prefs.gui_ask_unsaved) { + if (cf_has_unsaved_data(capture_file_.capFile())) { + if (context == Update) { + // We're being called from the software update window; + // don't spawn yet another dialog. Just try again later. + // XXX: The WinSparkle dialogs *aren't* modal, and a user + // can bring Wireshark to the foreground, close/save the + // file, and then click "Install Update" again, but it + // seems like many users don't expect that (and also don't + // know that Help->Check for Updates... exist, only knowing + // about the automatic check.) See #17658 and duplicates. + // Maybe we *should* spawn the dialog? + return false; + } + + QMessageBox msg_dialog; + QString question; + QString infotext; + QPushButton *save_button; + QPushButton *discard_button; + + msg_dialog.setIcon(QMessageBox::Question); + msg_dialog.setWindowTitle("Unsaved packets" UTF8_HORIZONTAL_ELLIPSIS); + + /* This file has unsaved data or there's a capture in + progress; ask the user whether to save the data. */ + if (capture_in_progress && context != Restart) { + question = tr("Do you want to stop the capture and save the captured packets%1?").arg(before_what); + infotext = tr("Your captured packets will be lost if you don't save them."); + } else if (capture_file_.capFile()->is_tempfile) { + if (context == Reload) { + // Reloading a tempfile will keep the packets, so this is not unsaved packets + question = tr("Do you want to save the changes you've made%1?").arg(before_what); + infotext = tr("Your changes will be lost if you don't save them."); + } else { + question = tr("Do you want to save the captured packets%1?").arg(before_what); + infotext = tr("Your captured packets will be lost if you don't save them."); + } + } else { + // No capture in progress and not a tempfile, so this is not unsaved packets + gchar *display_basename = g_filename_display_basename(capture_file_.capFile()->filename); + question = tr("Do you want to save the changes you've made to the capture file \"%1\"%2?").arg(display_basename, before_what); + infotext = tr("Your changes will be lost if you don't save them."); + g_free(display_basename); + } + + msg_dialog.setText(question); + msg_dialog.setInformativeText(infotext); + + // XXX Text comes from ui/gtk/stock_icons.[ch] + // Note that the button roles differ from the GTK+ version. + // Cancel = RejectRole + // Save = AcceptRole + // Don't Save = DestructiveRole + msg_dialog.addButton(QMessageBox::Cancel); + + if (capture_in_progress) { + QString save_button_text; + if (context == Restart) { + save_button_text = tr("Save before Continue"); + } else { + save_button_text = tr("Stop and Save"); + } + save_button = msg_dialog.addButton(save_button_text, QMessageBox::AcceptRole); + } else { + save_button = msg_dialog.addButton(QMessageBox::Save); + } + msg_dialog.setDefaultButton(save_button); + + QString discard_button_text; + if (capture_in_progress) { + switch (context) { + case Quit: + discard_button_text = tr("Stop and Quit &without Saving"); + break; + case Restart: + discard_button_text = tr("Continue &without Saving"); + break; + default: + discard_button_text = tr("Stop and Continue &without Saving"); + break; + } + } else { + switch (context) { + case Quit: + discard_button_text = tr("Quit &without Saving"); + break; + case Restart: + default: + discard_button_text = tr("Continue &without Saving"); + break; + } + } + discard_button = msg_dialog.addButton(discard_button_text, QMessageBox::DestructiveRole); + +#if defined(Q_OS_MAC) + /* + * In macOS, the "default button" is not necessarily the + * button that has the input focus; Enter/Return activates + * the default button, and the spacebar activates the button + * that has the input focus, and they might be different + * buttons. + * + * In a "do you want to save" dialog, for example, the + * "save" button is the default button, and the "don't + * save" button has the input focus, so you can press + * Enter/Return to save or space not to save (or Escape + * to dismiss the dialog). + * + * In Qt terms, this means "no auto-default", as auto-default + * makes the button with the input focus the default button, + * so that Enter/Return will activate it. + */ + QList<QAbstractButton *> buttons = msg_dialog.buttons(); + for (int i = 0; i < buttons.size(); ++i) { + QPushButton *button = static_cast<QPushButton *>(buttons.at(i));; + button->setAutoDefault(false); + } + + /* + * It also means that the "don't save" button should be the one + * initially given the focus. + */ + discard_button->setFocus(); +#endif + + msg_dialog.exec(); + /* According to the Qt doc: + * when using QMessageBox with custom buttons, exec() function returns an opaque value. + * + * Therefore we should use clickedButton() to determine which button was clicked. */ + + if (msg_dialog.clickedButton() == save_button) { +#ifdef HAVE_LIBPCAP + /* If there's a capture in progress, we have to stop the capture + and then do the save. */ + if (capture_in_progress) + captureStop(); +#endif + /* Save the file and close it */ + // XXX if no packets were captured, any unsaved comments set by + // the user are silently discarded because capFile() is null. + if (capture_file_.capFile() && saveCaptureFile(capture_file_.capFile(), true) == false) + return false; + do_close_file = true; + } else if (msg_dialog.clickedButton() == discard_button) { + /* Just close the file, discarding changes */ + do_close_file = true; + } else { + // cancelButton or some other unspecified button + return false; + } + } else { + /* Unchanged file or capturing with no packets */ + do_close_file = true; + } + } else { + /* User asked not to be bothered by those prompts, just close it. + XXX - should that apply only to saving temporary files? */ + do_close_file = true; + } + + /* + * Are we done with this file and should we close the file? + */ + if (do_close_file) { +#ifdef HAVE_LIBPCAP + /* If there's a capture in progress, we have to stop the capture + and then do the close. */ + if (capture_in_progress) + captureStop(); + else if (capture_file_.capFile() && capture_file_.capFile()->state == FILE_READ_IN_PROGRESS) { + /* + * When an offline capture is being read, mark it as aborted. + * cf_read will be responsible for actually closing the capture. + * + * We cannot just invoke cf_close here since cf_read is up in the + * call chain. (update_progress_dlg can end up processing the Quit + * event from the user which then ends up here.) + * See also the above "read_lock" check. + */ + capture_file_.capFile()->state = FILE_READ_ABORTED; + return true; + } +#endif + /* Clear MainWindow file name details */ + gbl_cur_main_window_->setMwFileName(""); + + /* captureStop() will close the file if not having any packets */ + if (capture_file_.capFile() && context != Restart && context != Reload) + // Don't really close if Restart or Reload + cf_close(capture_file_.capFile()); + } + + return true; /* File closed */ +} + +void WiresharkMainWindow::captureStop() { + stopCapture(); + + while (capture_file_.capFile() && (capture_file_.capFile()->state == FILE_READ_IN_PROGRESS || + capture_file_.capFile()->state == FILE_READ_PENDING)) { + WiresharkApplication::processEvents(); + } +} + +void WiresharkMainWindow::findTextCodecs() { + const QList<int> mibs = QTextCodec::availableMibs(); + QRegularExpression ibmRegExp("^IBM([0-9]+).*$"); + QRegularExpression iso8859RegExp("^ISO-8859-([0-9]+).*$"); + QRegularExpression windowsRegExp("^WINDOWS-([0-9]+).*$"); + QRegularExpressionMatch match; + for (int mib : mibs) { + QTextCodec *codec = QTextCodec::codecForMib(mib); + // QTextCodec::availableMibs() returns a list of hard-coded MIB + // numbers, it doesn't check if they are really available. ICU data may + // not have been compiled with support for all encodings. + if (!codec) { + continue; + } + + QString key = codec->name().toUpper(); + char rank; + + if (key.localeAwareCompare("IBM") < 0) { + rank = 1; + } else if ((match = ibmRegExp.match(key)).hasMatch()) { + rank = match.captured(1).size(); // Up to 5 + } else if (key.localeAwareCompare("ISO-8859-") < 0) { + rank = 6; + } else if ((match = iso8859RegExp.match(key)).hasMatch()) { + rank = 6 + match.captured(1).size(); // Up to 6 + 2 + } else if (key.localeAwareCompare("WINDOWS-") < 0) { + rank = 9; + } else if ((match = windowsRegExp.match(key)).hasMatch()) { + rank = 9 + match.captured(1).size(); // Up to 9 + 4 + } else { + rank = 14; + } + // This doesn't perfectly well order the IBM codecs because it's + // annoying to properly place IBM00858 and IBM00924 in the middle of + // code page numbers not zero padded to 5 digits. + // We could manipulate the key further to have more commonly used + // charsets earlier. IANA MIB ordering would be unxpected: + // https://www.iana.org/assignments/character-sets/character-sets.xml + // For data about use in HTTP (other protocols can be quite different): + // https://w3techs.com/technologies/overview/character_encoding + + key.prepend(char('0' + rank)); + // We use a map here because, due to backwards compatibility, + // the same QTextCodec may be returned for multiple MIBs, which + // happens for GBK/GB2312, EUC-KR/windows-949/UHC, and others. + text_codec_map_.insert(key, codec); + } +} + +void WiresharkMainWindow::initMainToolbarIcons() +{ + // Normally 16 px. Reflects current GTK+ behavior and other Windows apps. + int icon_size = style()->pixelMetric(QStyle::PM_SmallIconSize); +#if !defined(Q_OS_WIN) + // Force icons to 24x24 for now, otherwise actionFileOpen looks wonky. + // The macOS HIG specifies 32-pixel icons but they're a little too + // large IMHO. + icon_size = icon_size * 3 / 2; +#endif + main_ui_->mainToolBar->setIconSize(QSize(icon_size, icon_size)); + + // Toolbar actions. The GNOME HIG says that we should have a menu icon for each + // toolbar item but that clutters up our menu. Set menu icons sparingly. + + main_ui_->actionCaptureStart->setIcon(StockIcon("x-capture-start")); + main_ui_->actionCaptureStop->setIcon(StockIcon("x-capture-stop")); + main_ui_->actionCaptureRestart->setIcon(StockIcon("x-capture-restart")); + main_ui_->actionCaptureOptions->setIcon(StockIcon("x-capture-options")); + + // Menu icons are disabled in main_window.ui for these items. + main_ui_->actionFileOpen->setIcon(StockIcon("document-open")); + main_ui_->actionFileSave->setIcon(StockIcon("x-capture-file-save")); + main_ui_->actionFileClose->setIcon(StockIcon("x-capture-file-close")); + main_ui_->actionViewReload->setIcon(StockIcon("x-capture-file-reload")); + + main_ui_->actionEditFindPacket->setIcon(StockIcon("edit-find")); + main_ui_->actionGoPreviousPacket->setIcon(StockIcon("go-previous")); + main_ui_->actionGoNextPacket->setIcon(StockIcon("go-next")); + main_ui_->actionGoGoToPacket->setIcon(StockIcon("go-jump")); + main_ui_->actionGoFirstPacket->setIcon(StockIcon("go-first")); + main_ui_->actionGoLastPacket->setIcon(StockIcon("go-last")); + main_ui_->actionGoPreviousConversationPacket->setIcon(StockIcon("go-previous")); + main_ui_->actionGoNextConversationPacket->setIcon(StockIcon("go-next")); +#if defined(Q_OS_MAC) + main_ui_->actionGoPreviousConversationPacket->setShortcut(QKeySequence(Qt::META | Qt::Key_Comma)); + main_ui_->actionGoNextConversationPacket->setShortcut(QKeySequence(Qt::META | Qt::Key_Period)); +#endif + main_ui_->actionGoPreviousHistoryPacket->setIcon(StockIcon("go-previous")); + main_ui_->actionGoNextHistoryPacket->setIcon(StockIcon("go-next")); + main_ui_->actionGoAutoScroll->setIcon(StockIcon("x-stay-last")); + + main_ui_->actionViewColorizePacketList->setIcon(StockIcon("x-colorize-packets")); + + QList<QKeySequence> zi_seq = main_ui_->actionViewZoomIn->shortcuts(); + zi_seq << QKeySequence(Qt::CTRL | Qt::Key_Equal); + main_ui_->actionViewZoomIn->setIcon(StockIcon("zoom-in")); + main_ui_->actionViewZoomIn->setShortcuts(zi_seq); + main_ui_->actionViewZoomOut->setIcon(StockIcon("zoom-out")); + main_ui_->actionViewNormalSize->setIcon(StockIcon("zoom-original")); + main_ui_->actionViewResizeColumns->setIcon(StockIcon("x-resize-columns")); + + main_ui_->actionNewDisplayFilterExpression->setIcon(StockIcon("list-add")); +} + +void WiresharkMainWindow::initShowHideMainWidgets() +{ + if (show_hide_actions_) { + return; + } + + show_hide_actions_ = new QActionGroup(this); + QMap<QAction *, QWidget *> shmw_actions; + + show_hide_actions_->setExclusive(false); + shmw_actions[main_ui_->actionViewMainToolbar] = main_ui_->mainToolBar; + shmw_actions[main_ui_->actionViewFilterToolbar] = main_ui_->displayFilterToolBar; +#if defined(HAVE_LIBNL) && defined(HAVE_NL80211) + shmw_actions[main_ui_->actionViewWirelessToolbar] = main_ui_->wirelessToolBar; +#endif + shmw_actions[main_ui_->actionViewStatusBar] = main_ui_->statusBar; + shmw_actions[main_ui_->actionViewPacketList] = packet_list_; + shmw_actions[main_ui_->actionViewPacketDetails] = proto_tree_; + shmw_actions[main_ui_->actionViewPacketBytes] = byte_view_tab_; + shmw_actions[main_ui_->actionViewPacketDiagram] = packet_diagram_; + + foreach(QAction *shmwa, shmw_actions.keys()) { + shmwa->setData(QVariant::fromValue(shmw_actions[shmwa])); + show_hide_actions_->addAction(shmwa); + } + + // Initial hide the Interface Toolbar submenu + main_ui_->menuInterfaceToolbars->menuAction()->setVisible(false); + + /* Initially hide the additional toolbars menus */ + main_ui_->menuAdditionalToolbars->menuAction()->setVisible(false); + + connect(show_hide_actions_, SIGNAL(triggered(QAction*)), this, SLOT(showHideMainWidgets(QAction*))); +} + +void WiresharkMainWindow::initTimeDisplayFormatMenu() +{ + if (time_display_actions_) { + return; + } + + time_display_actions_ = new QActionGroup(this); + + td_actions[main_ui_->actionViewTimeDisplayFormatDateYMDandTimeOfDay] = TS_ABSOLUTE_WITH_YMD; + td_actions[main_ui_->actionViewTimeDisplayFormatDateYDOYandTimeOfDay] = TS_ABSOLUTE_WITH_YDOY; + td_actions[main_ui_->actionViewTimeDisplayFormatTimeOfDay] = TS_ABSOLUTE; + td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSinceEpoch] = TS_EPOCH; + td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSinceFirstCapturedPacket] = TS_RELATIVE; + td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSincePreviousCapturedPacket] = TS_DELTA; + td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSincePreviousDisplayedPacket] = TS_DELTA_DIS; + td_actions[main_ui_->actionViewTimeDisplayFormatUTCDateYMDandTimeOfDay] = TS_UTC_WITH_YMD; + td_actions[main_ui_->actionViewTimeDisplayFormatUTCDateYDOYandTimeOfDay] = TS_UTC_WITH_YDOY; + td_actions[main_ui_->actionViewTimeDisplayFormatUTCTimeOfDay] = TS_UTC; + + foreach(QAction* tda, td_actions.keys()) { + tda->setData(QVariant::fromValue(td_actions[tda])); + time_display_actions_->addAction(tda); + } + + connect(time_display_actions_, SIGNAL(triggered(QAction*)), this, SLOT(setTimestampFormat(QAction*))); +} + +void WiresharkMainWindow::initTimePrecisionFormatMenu() +{ + if (time_precision_actions_) { + return; + } + + time_precision_actions_ = new QActionGroup(this); + + tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionAutomatic] = TS_PREC_AUTO; + tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionSeconds] = TS_PREC_FIXED_SEC; + tp_actions[main_ui_->actionViewTimeDisplayFormatPrecision100Milliseconds] = TS_PREC_FIXED_100_MSEC; + tp_actions[main_ui_->actionViewTimeDisplayFormatPrecision10Milliseconds] = TS_PREC_FIXED_10_MSEC; + tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionMilliseconds] = TS_PREC_FIXED_MSEC; + tp_actions[main_ui_->actionViewTimeDisplayFormatPrecision100Microseconds] = TS_PREC_FIXED_100_USEC; + tp_actions[main_ui_->actionViewTimeDisplayFormatPrecision10Microseconds] = TS_PREC_FIXED_10_USEC; + tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionMicroseconds] = TS_PREC_FIXED_USEC; + tp_actions[main_ui_->actionViewTimeDisplayFormatPrecision100Nanoseconds] = TS_PREC_FIXED_100_NSEC; + tp_actions[main_ui_->actionViewTimeDisplayFormatPrecision10Nanoseconds] = TS_PREC_FIXED_10_NSEC; + tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionNanoseconds] = TS_PREC_FIXED_NSEC; + + foreach(QAction* tpa, tp_actions.keys()) { + tpa->setData(QVariant::fromValue(tp_actions[tpa])); + time_precision_actions_->addAction(tpa); + } + + connect(time_precision_actions_, SIGNAL(triggered(QAction*)), this, SLOT(setTimestampPrecision(QAction*))); +} + +// Menu items which will be disabled when we freeze() and whose state will +// be restored when we thaw(). Add to the list as needed. +void WiresharkMainWindow::initFreezeActions() +{ + QList<QAction *> freeze_actions = QList<QAction *>() + << main_ui_->actionFileClose + << main_ui_->actionViewReload + << main_ui_->actionEditMarkPacket + << main_ui_->actionEditMarkAllDisplayed + << main_ui_->actionEditUnmarkAllDisplayed + << main_ui_->actionEditIgnorePacket + << main_ui_->actionEditIgnoreAllDisplayed + << main_ui_->actionEditUnignoreAllDisplayed + << main_ui_->actionEditSetTimeReference + << main_ui_->actionEditUnsetAllTimeReferences; + + foreach(QAction *action, freeze_actions) { + freeze_actions_ << QPair<QAction *, bool>(action, false); + } +} + +void WiresharkMainWindow::initConversationMenus() +{ + int i; + + QList<QAction *> cc_actions = QList<QAction *>() + << main_ui_->actionViewColorizeConversation1 << main_ui_->actionViewColorizeConversation2 + << main_ui_->actionViewColorizeConversation3 << main_ui_->actionViewColorizeConversation4 + << main_ui_->actionViewColorizeConversation5 << main_ui_->actionViewColorizeConversation6 + << main_ui_->actionViewColorizeConversation7 << main_ui_->actionViewColorizeConversation8 + << main_ui_->actionViewColorizeConversation9 << main_ui_->actionViewColorizeConversation10; + + for (GList *conv_filter_list_entry = packet_conv_filter_list; conv_filter_list_entry; conv_filter_list_entry = gxx_list_next(conv_filter_list_entry)) { + // Main menu items + conversation_filter_t* conv_filter = gxx_list_data(conversation_filter_t *, conv_filter_list_entry); + ConversationAction *conv_action = new ConversationAction(main_ui_->menuConversationFilter, conv_filter); + main_ui_->menuConversationFilter->addAction(conv_action); + + connect(this, SIGNAL(packetInfoChanged(_packet_info*)), conv_action, SLOT(setPacketInfo(_packet_info*))); + connect(conv_action, SIGNAL(triggered()), this, SLOT(applyConversationFilter()), Qt::QueuedConnection); + + // Packet list context menu items + packet_list_->conversationMenu()->addAction(conv_action); + + QMenu *submenu = packet_list_->colorizeMenu()->addMenu(conv_action->text()); + i = 1; + + foreach(QAction *cc_action, cc_actions) { + conv_action = new ConversationAction(submenu, conv_filter); + conv_action->setText(cc_action->text()); + conv_action->setIcon(cc_action->icon()); + conv_action->setColorNumber(i++); + submenu->addAction(conv_action); + connect(this, SIGNAL(packetInfoChanged(_packet_info*)), conv_action, SLOT(setPacketInfo(_packet_info*))); + connect(conv_action, SIGNAL(triggered()), this, SLOT(colorizeActionTriggered())); + } + + conv_action = new ConversationAction(submenu, conv_filter); + conv_action->setText(main_ui_->actionViewColorizeNewColoringRule->text()); + submenu->addAction(conv_action); + connect(this, SIGNAL(packetInfoChanged(_packet_info*)), conv_action, SLOT(setPacketInfo(_packet_info*))); + connect(conv_action, SIGNAL(triggered()), this, SLOT(colorizeActionTriggered())); + + // Proto tree conversation menu is filled in in ProtoTree::contextMenuEvent. + // We should probably do that here. + } + + // Proto tree colorization items + i = 1; + ColorizeAction *colorize_action; + foreach(QAction *cc_action, cc_actions) { + colorize_action = new ColorizeAction(proto_tree_->colorizeMenu()); + colorize_action->setText(cc_action->text()); + colorize_action->setIcon(cc_action->icon()); + colorize_action->setColorNumber(i++); + proto_tree_->colorizeMenu()->addAction(colorize_action); + connect(this, SIGNAL(fieldFilterChanged(QByteArray)), colorize_action, SLOT(setFieldFilter(QByteArray))); + connect(colorize_action, SIGNAL(triggered()), this, SLOT(colorizeActionTriggered())); + } + + colorize_action = new ColorizeAction(proto_tree_->colorizeMenu()); + colorize_action->setText(main_ui_->actionViewColorizeNewColoringRule->text()); + proto_tree_->colorizeMenu()->addAction(colorize_action); + connect(this, SIGNAL(fieldFilterChanged(QByteArray)), colorize_action, SLOT(setFieldFilter(QByteArray))); + connect(colorize_action, SIGNAL(triggered()), this, SLOT(colorizeActionTriggered())); +} + +bool WiresharkMainWindow::addExportObjectsMenuItem(const void *, void *value, void *userdata) +{ + register_eo_t *eo = (register_eo_t*)value; + WiresharkMainWindow *window = (WiresharkMainWindow*)userdata; + + ExportObjectAction *export_action = new ExportObjectAction(window->main_ui_->menuFileExportObjects, eo); + window->main_ui_->menuFileExportObjects->addAction(export_action); + + //initially disable until a file is loaded (then file signals will take over) + export_action->setEnabled(false); + + connect(&window->capture_file_, SIGNAL(captureEvent(CaptureEvent)), export_action, SLOT(captureFileEvent(CaptureEvent))); + connect(export_action, SIGNAL(triggered()), window, SLOT(applyExportObject())); + return FALSE; +} + +void WiresharkMainWindow::initExportObjectsMenus() +{ + eo_iterate_tables(addExportObjectsMenuItem, this); +} + +bool WiresharkMainWindow::addFollowStreamMenuItem(const void *key, void *value, void *userdata) +{ + const char *short_name = (const char*)key; + register_follow_t *follow = (register_follow_t*)value; + WiresharkMainWindow *window = (WiresharkMainWindow*)userdata; + + FollowStreamAction *follow_action = new FollowStreamAction(window->main_ui_->menuFollow, follow); + window->main_ui_->menuFollow->addAction(follow_action); + + follow_action->setEnabled(false); + + /* Special features for some of the built in follow types, like + * shortcuts and overriding the name. XXX: Should these go in + * FollowStreamAction, or should some of these (e.g. TCP and UDP) + * be registered in initFollowStreamMenus so that they can be + * on the top of the menu list too? + */ + if (g_strcmp0(short_name, "TCP") == 0) { + follow_action->setShortcut(Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_T); + } else if (g_strcmp0(short_name, "UDP") == 0) { + follow_action->setShortcut(Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_U); + } else if (g_strcmp0(short_name, "DCCP") == 0) { + /* XXX: Not sure this one is widely enough used to need a shortcut. */ + follow_action->setShortcut(Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_E); + } else if (g_strcmp0(short_name, "TLS") == 0) { + follow_action->setShortcut(Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_S); + } else if (g_strcmp0(short_name, "HTTP") == 0) { + follow_action->setShortcut(Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_H); + } else if (g_strcmp0(short_name, "HTTP2") == 0) { + follow_action->setText(tr("HTTP/2 Stream")); + } else if (g_strcmp0(short_name, "SIP") == 0) { + follow_action->setText(tr("SIP Call")); + } + + connect(follow_action, &QAction::triggered, window, + [window, follow]() { window->openFollowStreamDialog(get_follow_proto_id(follow)); }, + Qt::QueuedConnection); + return FALSE; +} + +void WiresharkMainWindow::initFollowStreamMenus() +{ + /* This puts them all in the menus in alphabetical order. */ + follow_iterate_followers(addFollowStreamMenuItem, this); +} + +// Titlebar +void WiresharkMainWindow::setTitlebarForCaptureFile() +{ + if (capture_file_.capFile() && capture_file_.capFile()->filename) { + setWSWindowTitle(QString("[*]%1").arg(capture_file_.fileDisplayName())); + // + // XXX - on non-Mac platforms, put in the application + // name? Or do so only for temporary files? + // + if (!capture_file_.capFile()->is_tempfile) { + // + // Set the file path; that way, for macOS, it'll set the + // "proxy icon". + // + setWindowFilePath(capture_file_.filePath()); + } + setWindowModified(cf_has_unsaved_data(capture_file_.capFile())); + } else { + /* We have no capture file. */ + setWSWindowTitle(); + } +} + +QString WiresharkMainWindow::replaceWindowTitleVariables(QString title) +{ + title.replace("%P", get_profile_name()); + title.replace("%V", get_ws_vcs_version_info()); + + if (title.contains("%F")) { + // %F is file path of the capture file. + if (capture_file_.capFile()) { + // get_dirname() will overwrite the argument so make a copy first + char *filename = g_strdup(capture_file_.capFile()->filename); + QString file(get_dirname(filename)); + g_free(filename); +#ifndef _WIN32 + // Substitute HOME with ~ + QString homedir(g_getenv("HOME")); + if (!homedir.isEmpty()) { + homedir.remove(QRegularExpression("[/]+$")); + file.replace(homedir, "~"); + } +#endif + title.replace("%F", file); + } else { + // No file loaded, no folder name + title.remove("%F"); + } + } + + if (title.contains("%S")) { + // %S is a conditional separator (" - ") that only shows when surrounded by variables + // with values or static text. Remove repeating, leading and trailing separators. + title.replace(QRegularExpression("(%S)+"), "%S"); + title.remove(QRegularExpression("^%S|%S$")); +#ifdef __APPLE__ + // On macOS we separate with a unicode em dash + title.replace("%S", " " UTF8_EM_DASH " "); +#else + title.replace("%S", " - "); +#endif + } + + return title; +} + +void WiresharkMainWindow::setWSWindowTitle(QString title) +{ + if (title.isEmpty()) { + title = tr("The Wireshark Network Analyzer"); + } + + if (prefs.gui_prepend_window_title && prefs.gui_prepend_window_title[0]) { + QString custom_title = replaceWindowTitleVariables(prefs.gui_prepend_window_title); + if (custom_title.length() > 0) { + title.prepend(QString("[%1] ").arg(custom_title)); + } + } + + if (prefs.gui_window_title && prefs.gui_window_title[0]) { + QString custom_title = replaceWindowTitleVariables(prefs.gui_window_title); + if (custom_title.length() > 0) { +#ifdef __APPLE__ + // On macOS we separate the titles with a unicode em dash + title.append(QString(" %1 %2").arg(UTF8_EM_DASH).arg(custom_title)); +#else + title.append(QString(" [%1]").arg(custom_title)); +#endif + } + } + + setWindowTitle(title); + setWindowFilePath(NULL); +} + +void WiresharkMainWindow::setTitlebarForCaptureInProgress() +{ + if (capture_file_.capFile()) { + setWSWindowTitle(tr("Capturing from %1").arg(cf_get_tempfile_source(capture_file_.capFile()))); + } else { + /* We have no capture in progress. */ + setWSWindowTitle(); + } +} + +// Menu state + +/* Enable or disable menu items based on whether you have a capture file + you've finished reading and, if you have one, whether it's been saved + and whether it could be saved except by copying the raw packet data. */ +void WiresharkMainWindow::setMenusForCaptureFile(bool force_disable) +{ + bool enable = true; + bool can_write = false; + bool can_save = false; + bool can_save_as = false; + + if (force_disable || capture_file_.capFile() == NULL || capture_file_.capFile()->state == FILE_READ_IN_PROGRESS || capture_file_.capFile()->state == FILE_READ_PENDING) { + /* We have no capture file or we're currently reading a file */ + enable = false; + } else { + /* We have a capture file. Can we write or save? */ + can_write = cf_can_write_with_wiretap(capture_file_.capFile()); + can_save = cf_can_save(capture_file_.capFile()); + can_save_as = cf_can_save_as(capture_file_.capFile()); + } + + main_ui_->actionViewReload_as_File_Format_or_Capture->setEnabled(enable); + main_ui_->actionFileMerge->setEnabled(can_write); + main_ui_->actionFileClose->setEnabled(enable); + main_ui_->actionFileSave->setEnabled(can_save); + main_ui_->actionFileSaveAs->setEnabled(can_save_as); + main_ui_->actionStatisticsCaptureFileProperties->setEnabled(enable); + /* The Protocol Hierarchy statistics run on all the packets that + * pass the current filter, don't enable if a read or rescan is + * still in progress. + */ + main_ui_->actionStatisticsProtocolHierarchy->setEnabled(enable); + /* + * "Export Specified Packets..." should be available only if + * we can write the file out in at least one format. + */ + main_ui_->actionFileExportPackets->setEnabled(can_write); + + main_ui_->actionFileExportAsCArrays->setEnabled(enable); + main_ui_->actionFileExportAsCSV->setEnabled(enable); + main_ui_->actionFileExportAsPDML->setEnabled(enable); + main_ui_->actionFileExportAsPlainText->setEnabled(enable); + main_ui_->actionFileExportAsPSML->setEnabled(enable); + main_ui_->actionFileExportAsJSON->setEnabled(enable); + + main_ui_->actionFileExportPDU->setEnabled(enable); + main_ui_->actionFileStripHeaders->setEnabled(enable); + /* XXX: "Export TLS Session Keys..." should be enabled only if + * ssl_session_key_count() > 0. + */ + main_ui_->actionFileExportTLSSessionKeys->setEnabled(enable); + + foreach(QAction *eo_action, main_ui_->menuFileExportObjects->actions()) { + eo_action->setEnabled(enable); + } + + main_ui_->actionViewReload->setEnabled(enable); + +#ifdef HAVE_SOFTWARE_UPDATE + // We might want to enable or disable automatic checks here as well. + update_action_->setEnabled(!can_save); +#endif +} + +void WiresharkMainWindow::setMenusForCaptureInProgress(bool capture_in_progress) { + /* Either a capture was started or stopped; in either case, it's not + in the process of stopping, so allow quitting. */ + + main_ui_->actionFileOpen->setEnabled(!capture_in_progress); + main_ui_->menuOpenRecentCaptureFile->setEnabled(!capture_in_progress); + + main_ui_->actionFileExportAsCArrays->setEnabled(capture_in_progress); + main_ui_->actionFileExportAsCSV->setEnabled(capture_in_progress); + main_ui_->actionFileExportAsPDML->setEnabled(capture_in_progress); + main_ui_->actionFileExportAsPlainText->setEnabled(capture_in_progress); + main_ui_->actionFileExportAsPSML->setEnabled(capture_in_progress); + main_ui_->actionFileExportAsJSON->setEnabled(capture_in_progress); + + main_ui_->actionFileExportPDU->setEnabled(!capture_in_progress); + main_ui_->actionFileStripHeaders->setEnabled(!capture_in_progress); + main_ui_->actionFileExportTLSSessionKeys->setEnabled(capture_in_progress); + + foreach(QAction *eo_action, main_ui_->menuFileExportObjects->actions()) { + eo_action->setEnabled(capture_in_progress); + } + + main_ui_->menuFileSet->setEnabled(!capture_in_progress); + main_ui_->actionFileQuit->setEnabled(true); +#ifdef HAVE_SOFTWARE_UPDATE + // We might want to enable or disable automatic checks here as well. + update_action_->setEnabled(!capture_in_progress); +#endif + + main_ui_->actionStatisticsCaptureFileProperties->setEnabled(capture_in_progress); + + // XXX Fix packet list heading menu sensitivity + // set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/SortAscending", + // !capture_in_progress); + // set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/SortDescending", + // !capture_in_progress); + // set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/NoSorting", + // !capture_in_progress); + +#ifdef HAVE_LIBPCAP + main_ui_->actionCaptureOptions->setEnabled(!capture_in_progress); + main_ui_->actionCaptureStart->setEnabled(!capture_in_progress); + main_ui_->actionCaptureStart->setChecked(capture_in_progress); + main_ui_->actionCaptureStop->setEnabled(capture_in_progress); + main_ui_->actionCaptureRestart->setEnabled(capture_in_progress); + main_ui_->actionCaptureRefreshInterfaces->setEnabled(!capture_in_progress); +#endif /* HAVE_LIBPCAP */ + +} + +void WiresharkMainWindow::setMenusForCaptureStopping() { + main_ui_->actionFileQuit->setEnabled(false); +#ifdef HAVE_SOFTWARE_UPDATE + update_action_->setEnabled(false); +#endif + main_ui_->actionStatisticsCaptureFileProperties->setEnabled(false); +#ifdef HAVE_LIBPCAP + main_ui_->actionCaptureStart->setChecked(false); + main_ui_->actionCaptureStop->setEnabled(false); + main_ui_->actionCaptureRestart->setEnabled(false); +#endif /* HAVE_LIBPCAP */ +} + +void WiresharkMainWindow::setForCapturedPackets(bool have_captured_packets) +{ + main_ui_->actionFilePrint->setEnabled(have_captured_packets); + +// set_menu_sensitivity(ui_manager_packet_list_menu, "/PacketListMenuPopup/Print", +// have_captured_packets); + + main_ui_->actionEditFindPacket->setEnabled(have_captured_packets); + main_ui_->actionEditFindNext->setEnabled(have_captured_packets); + main_ui_->actionEditFindPrevious->setEnabled(have_captured_packets); + + main_ui_->actionGoGoToPacket->setEnabled(have_captured_packets); + main_ui_->actionGoPreviousPacket->setEnabled(have_captured_packets); + main_ui_->actionGoNextPacket->setEnabled(have_captured_packets); + main_ui_->actionGoFirstPacket->setEnabled(have_captured_packets); + main_ui_->actionGoLastPacket->setEnabled(have_captured_packets); + main_ui_->actionGoNextConversationPacket->setEnabled(have_captured_packets); + main_ui_->actionGoPreviousConversationPacket->setEnabled(have_captured_packets); + + main_ui_->actionViewZoomIn->setEnabled(have_captured_packets); + main_ui_->actionViewZoomOut->setEnabled(have_captured_packets); + main_ui_->actionViewNormalSize->setEnabled(have_captured_packets); + main_ui_->actionViewResizeColumns->setEnabled(have_captured_packets); + + main_ui_->actionStatisticsCaptureFileProperties->setEnabled(have_captured_packets); + main_ui_->actionStatisticsProtocolHierarchy->setEnabled(have_captured_packets); + main_ui_->actionStatisticsIOGraph->setEnabled(have_captured_packets); +} + +void WiresharkMainWindow::setMenusForFileSet(bool enable_list_files) { + bool enable_next = fileset_get_next() != NULL && enable_list_files; + bool enable_prev = fileset_get_previous() != NULL && enable_list_files; + + main_ui_->actionFileSetListFiles->setEnabled(enable_list_files); + main_ui_->actionFileSetNextFile->setEnabled(enable_next); + main_ui_->actionFileSetPreviousFile->setEnabled(enable_prev); +} + +void WiresharkMainWindow::setWindowIcon(const QIcon &icon) { + mainApp->setWindowIcon(icon); + QMainWindow::setWindowIcon(icon); +} + +void WiresharkMainWindow::updateForUnsavedChanges() { + setTitlebarForCaptureFile(); + setMenusForCaptureFile(); +} + +void WiresharkMainWindow::changeEvent(QEvent* event) +{ + if (0 != event) + { + switch (event->type()) + { + case QEvent::LanguageChange: + main_ui_->retranslateUi(this); + // make sure that the "Clear Menu" item is retranslated + mainApp->emitAppSignal(WiresharkApplication::RecentCapturesChanged); + setTitlebarForCaptureFile(); + break; + case QEvent::LocaleChange: { + QString locale = QLocale::system().name(); + locale.truncate(locale.lastIndexOf('_')); + mainApp->loadLanguage(locale); + } + break; + case QEvent::WindowStateChange: + main_ui_->actionViewFullScreen->setChecked(this->isFullScreen()); + break; + default: + break; + } + } + QMainWindow::changeEvent(event); +} + +/* Update main window items based on whether there's a capture in progress. */ +void WiresharkMainWindow::setForCaptureInProgress(bool capture_in_progress, bool handle_toolbars, GArray *ifaces) +{ + setMenusForCaptureInProgress(capture_in_progress); + +#if defined(HAVE_LIBNL) && defined(HAVE_NL80211) + wireless_frame_->setCaptureInProgress(capture_in_progress); +#endif + +#ifdef HAVE_LIBPCAP + packet_list_->setCaptureInProgress(capture_in_progress, main_ui_->actionGoAutoScroll->isChecked()); + +// set_capture_if_dialog_for_capture_in_progress(capture_in_progress); +#endif + + if (handle_toolbars) { + QList<InterfaceToolbar *> toolbars = findChildren<InterfaceToolbar *>(); + foreach(InterfaceToolbar *toolbar, toolbars) { + if (capture_in_progress) { + toolbar->startCapture(ifaces); + } else { + toolbar->stopCapture(); + } + } + } +} + +void WiresharkMainWindow::addMenuActions(QList<QAction *> &actions, int menu_group) +{ + foreach(QAction *action, actions) { + switch (menu_group) { + case REGISTER_PACKET_ANALYZE_GROUP_UNSORTED: + case REGISTER_PACKET_STAT_GROUP_UNSORTED: + main_ui_->menuStatistics->insertAction( + main_ui_->actionStatistics_REGISTER_STAT_GROUP_UNSORTED, + action); + break; + case REGISTER_STAT_GROUP_RESPONSE_TIME: + main_ui_->menuServiceResponseTime->addAction(action); + break; + case REGISTER_STAT_GROUP_RSERPOOL: + main_ui_->menuRSerPool->addAction(action); + break; + case REGISTER_STAT_GROUP_TELEPHONY: + main_ui_->menuTelephony->addAction(action); + break; + case REGISTER_STAT_GROUP_TELEPHONY_ANSI: + main_ui_->menuANSI->addAction(action); + break; + case REGISTER_STAT_GROUP_TELEPHONY_GSM: + main_ui_->menuGSM->addAction(action); + break; + case REGISTER_STAT_GROUP_TELEPHONY_LTE: + main_ui_->menuLTE->addAction(action); + break; + case REGISTER_STAT_GROUP_TELEPHONY_MTP3: + main_ui_->menuMTP3->addAction(action); + break; + case REGISTER_TOOLS_GROUP_UNSORTED: + { + // Allow the creation of submenus. Mimics the behavor of + // ui/gtk/main_menubar.c:add_menu_item_to_main_menubar + // and GtkUIManager. + // + // For now we limit the insanity to the "Tools" menu. + QStringList menu_path = action->text().split('/'); + QMenu *cur_menu = main_ui_->menuTools; + while (menu_path.length() > 1) { + QString menu_title = menu_path.takeFirst(); + QMenu *submenu = cur_menu->findChild<QMenu *>(menu_title.toLower(), Qt::FindDirectChildrenOnly); + if (!submenu) { + submenu = cur_menu->addMenu(menu_title); + submenu->setObjectName(menu_title.toLower()); + } + cur_menu = submenu; + } + action->setText(menu_path.last()); + cur_menu->addAction(action); + break; + } + default: + // Skip log items. + return; + } + + // Connect each action type to its corresponding slot. We to + // distinguish various types of actions. Setting their objectName + // seems to work OK. + if (action->objectName() == TapParameterDialog::actionName()) { + connect(action, SIGNAL(triggered(bool)), this, SLOT(openTapParameterDialog())); + } else if (action->objectName() == FunnelStatistics::actionName()) { + connect(action, SIGNAL(triggered(bool)), funnel_statistics_, SLOT(funnelActionTriggered())); + } + } +} +void WiresharkMainWindow::removeMenuActions(QList<QAction *> &actions, int menu_group) +{ + foreach(QAction *action, actions) { + switch (menu_group) { + case REGISTER_PACKET_ANALYZE_GROUP_UNSORTED: + case REGISTER_PACKET_STAT_GROUP_UNSORTED: + main_ui_->menuStatistics->removeAction(action); + break; + case REGISTER_STAT_GROUP_RESPONSE_TIME: + main_ui_->menuServiceResponseTime->removeAction(action); + break; + case REGISTER_STAT_GROUP_RSERPOOL: + main_ui_->menuRSerPool->removeAction(action); + break; + case REGISTER_STAT_GROUP_TELEPHONY: + main_ui_->menuTelephony->removeAction(action); + break; + case REGISTER_STAT_GROUP_TELEPHONY_ANSI: + main_ui_->menuANSI->removeAction(action); + break; + case REGISTER_STAT_GROUP_TELEPHONY_GSM: + main_ui_->menuGSM->removeAction(action); + break; + case REGISTER_STAT_GROUP_TELEPHONY_LTE: + main_ui_->menuLTE->removeAction(action); + break; + case REGISTER_STAT_GROUP_TELEPHONY_MTP3: + main_ui_->menuMTP3->removeAction(action); + break; + case REGISTER_TOOLS_GROUP_UNSORTED: + { + // Allow removal of submenus. + // For now we limit the insanity to the "Tools" menu. + QStringList menu_path = action->text().split('/'); + QMenu *cur_menu = main_ui_->menuTools; + while (menu_path.length() > 1) { + QString menu_title = menu_path.takeFirst(); + QMenu *submenu = cur_menu->findChild<QMenu *>(menu_title.toLower(), Qt::FindDirectChildrenOnly); + cur_menu = submenu; + } + cur_menu->removeAction(action); + // Remove empty submenus. + while (cur_menu != main_ui_->menuTools) { + QMenu *empty_menu = (cur_menu->isEmpty() ? cur_menu : NULL); + cur_menu = dynamic_cast<QMenu *>(cur_menu->parent()); + delete empty_menu; + } + break; + } + default: +// qDebug() << "FIX: Remove" << action->text() << "from the menu"; + break; + } + } +} + +void WiresharkMainWindow::addDynamicMenus() +{ + // Manual additions + mainApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_GSM, main_ui_->actionTelephonyGsmMapSummary); + mainApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_LTE, main_ui_->actionTelephonyLteMacStatistics); + mainApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_LTE, main_ui_->actionTelephonyLteRlcStatistics); + mainApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_LTE, main_ui_->actionTelephonyLteRlcGraph); + mainApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_MTP3, main_ui_->actionTelephonyMtp3Summary); + mainApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY, main_ui_->actionTelephonySipFlows); + + // Fill in each menu + foreach(register_stat_group_t menu_group, menu_groups_) { + QList<QAction *>actions = mainApp->dynamicMenuGroupItems(menu_group); + addMenuActions(actions, menu_group); + } + + // Empty menus don't show up: https://bugreports.qt.io/browse/QTBUG-33728 + // We've added a placeholder in order to make sure some menus are visible. + // Hide them as needed. + if (mainApp->dynamicMenuGroupItems(REGISTER_STAT_GROUP_TELEPHONY_ANSI).length() > 0) { + main_ui_->actionTelephonyANSIPlaceholder->setVisible(false); + } + if (mainApp->dynamicMenuGroupItems(REGISTER_STAT_GROUP_TELEPHONY_GSM).length() > 0) { + main_ui_->actionTelephonyGSMPlaceholder->setVisible(false); + } + if (mainApp->dynamicMenuGroupItems(REGISTER_STAT_GROUP_TELEPHONY_LTE).length() > 0) { + main_ui_->actionTelephonyLTEPlaceholder->setVisible(false); + } + if (mainApp->dynamicMenuGroupItems(REGISTER_STAT_GROUP_TELEPHONY_MTP3).length() > 0) { + main_ui_->actionTelephonyMTP3Placeholder->setVisible(false); + } +} + +void WiresharkMainWindow::reloadDynamicMenus() +{ + foreach(register_stat_group_t menu_group, menu_groups_) { + QList<QAction *>actions = mainApp->removedMenuGroupItems(menu_group); + removeMenuActions(actions, menu_group); + + actions = mainApp->addedMenuGroupItems(menu_group); + addMenuActions(actions, menu_group); + } + + mainApp->clearAddedMenuGroupItems(); + mainApp->clearRemovedMenuGroupItems(); +} + +void WiresharkMainWindow::externalMenuHelper(ext_menu_t * menu, QMenu * subMenu, gint depth) +{ + QAction * itemAction = Q_NULLPTR; + ext_menubar_t * item = Q_NULLPTR; + GList * children = Q_NULLPTR; + + /* There must exists an xpath parent */ + Q_ASSERT(subMenu != NULL); + + /* If the depth counter exceeds, something must have gone wrong */ + Q_ASSERT(depth < EXT_MENUBAR_MAX_DEPTH); + + children = menu->children; + /* Iterate the child entries */ + while (children && children->data) { + item = gxx_list_data(ext_menubar_t *, children); + + if (item->type == EXT_MENUBAR_MENU) { + /* Handle Submenu entry */ + this->externalMenuHelper(item, subMenu->addMenu(item->label), depth++); + } else if (item->type == EXT_MENUBAR_SEPARATOR) { + subMenu->addSeparator(); + } else if (item->type == EXT_MENUBAR_ITEM || item->type == EXT_MENUBAR_URL) { + itemAction = subMenu->addAction(item->name); + itemAction->setData(QVariant::fromValue(static_cast<void *>(item))); + itemAction->setText(item->label); + connect(itemAction, &QAction::triggered, this, &WiresharkMainWindow::externalMenuItemTriggered); + } + + /* Iterate Loop */ + children = gxx_list_next(children); + } +} + +QMenu * WiresharkMainWindow::searchSubMenu(QString objectName) +{ + QList<QMenu*> lst; + + if (objectName.length() > 0) { + QString searchName = QString("menu") + objectName; + + lst = main_ui_->menuBar->findChildren<QMenu*>(); + foreach(QMenu* m, lst) { + if (QString::compare(m->objectName(), searchName) == 0) + return m; + } + } + + return 0; +} + +void WiresharkMainWindow::addPluginIFStructures() +{ + GList *user_menu = ext_menubar_get_entries(); + + while (user_menu && user_menu->data) { + QMenu *subMenu = Q_NULLPTR; + ext_menu_t *menu = gxx_list_data(ext_menu_t *, user_menu); + + /* On this level only menu items should exist. Not doing an assert here, + * as it could be an honest mistake */ + if (menu->type != EXT_MENUBAR_MENU) { + user_menu = gxx_list_next(user_menu); + continue; + } + + /* Create main submenu and add it to the menubar */ + if (menu->parent_menu) { + QMenu *sortUnderneath = searchSubMenu(QString(menu->parent_menu)); + if (sortUnderneath) + subMenu = sortUnderneath->addMenu(menu->label); + } + + if (!subMenu) + subMenu = main_ui_->menuBar->addMenu(menu->label); + + /* This will generate the action structure for each menu. It is recursive, + * therefore a sub-routine, and we have a depth counter to prevent endless loops. */ + this->externalMenuHelper(menu, subMenu, 0); + + /* Iterate Loop */ + user_menu = gxx_list_next(user_menu); + } + + int cntToolbars = 0; + + QMenu *tbMenu = main_ui_->menuAdditionalToolbars; + GList *if_toolbars = ext_toolbar_get_entries(); + while (if_toolbars && if_toolbars->data) { + ext_toolbar_t *toolbar = gxx_list_data(ext_toolbar_t*, if_toolbars); + + if (toolbar->type != EXT_TOOLBAR_BAR) { + if_toolbars = gxx_list_next(if_toolbars); + continue; + } + + bool visible = g_list_find_custom(recent.gui_additional_toolbars, toolbar->name, reinterpret_cast<GCompareFunc>(strcmp)) ? true : false; + + AdditionalToolBar *ifToolBar = AdditionalToolBar::create(this, toolbar); + + if (ifToolBar) { + ifToolBar->setVisible(visible); + + QAction *iftbAction = new QAction(QString(toolbar->name), this); + iftbAction->setToolTip(toolbar->tooltip); + iftbAction->setEnabled(true); + iftbAction->setCheckable(true); + iftbAction->setChecked(visible); + iftbAction->setToolTip(tr("Show or hide the toolbar")); + iftbAction->setData(VariantPointer<ext_toolbar_t>::asQVariant(toolbar)); + + QAction *before = Q_NULLPTR; + + foreach(QAction *action, tbMenu->actions()) { + /* Ensure we add the menu entries in sorted order */ + if (action->text().compare(toolbar->name, Qt::CaseInsensitive) > 0) { + before = action; + break; + } + } + + tbMenu->insertAction(before, iftbAction); + + addToolBar(Qt::TopToolBarArea, ifToolBar); + insertToolBarBreak(ifToolBar); + + if (show_hide_actions_) + show_hide_actions_->addAction(iftbAction); + + cntToolbars++; + } + + if_toolbars = gxx_list_next(if_toolbars); + } + + if (cntToolbars) + tbMenu->menuAction()->setVisible(true); +} + +void WiresharkMainWindow::removeAdditionalToolbar(QString toolbarName) +{ + if (toolbarName.length() == 0) + return; + + QList<QToolBar *> toolbars = findChildren<QToolBar *>(); + foreach(QToolBar *tb, toolbars) { + AdditionalToolBar *ifToolBar = dynamic_cast<AdditionalToolBar *>(tb); + + if (ifToolBar && ifToolBar->menuName().compare(toolbarName)) { + GList *entry = g_list_find_custom(recent.gui_additional_toolbars, qUtf8Printable(ifToolBar->menuName()), reinterpret_cast<GCompareFunc>(strcmp)); + if (entry) { + recent.gui_additional_toolbars = g_list_remove(recent.gui_additional_toolbars, entry->data); + } + QList<QAction *> actions = main_ui_->menuAdditionalToolbars->actions(); + foreach(QAction *action, actions) { + ext_toolbar_t *item = VariantPointer<ext_toolbar_t>::asPtr(action->data()); + if (item && ifToolBar->menuName().compare(item->name)) { + if (show_hide_actions_) + show_hide_actions_->removeAction(action); + main_ui_->menuAdditionalToolbars->removeAction(action); + } + } + break; + } + } + +} + +QString WiresharkMainWindow::getMwFileName() +{ + return mwFileName_; +} + +void WiresharkMainWindow::setMwFileName(QString fileName) +{ + mwFileName_ = fileName; + return; +} + +// Finds rtp id for selected stream and adds it to stream_ids +// If reverse is set, tries to find reverse stream and other streams +// bundled on the same RTP session too +// Return error string if error happens +// +// Note: Caller must free each returned rtpstream_info_t +QString WiresharkMainWindow::findRtpStreams(QVector<rtpstream_id_t *> *stream_ids, bool reverse) +{ + rtpstream_tapinfo_t tapinfo; + rtpstream_id_t *new_id; + const gchar filter_text[] = "rtp && rtp.version == 2 && rtp.ssrc && (ip || ipv6)"; + dfilter_t *sfcode; + df_error_t *df_err = NULL; + + /* Try to get the hfid for "rtp.ssrc". */ + int hfid_rtp_ssrc = proto_registrar_get_id_byname("rtp.ssrc"); + if (hfid_rtp_ssrc == -1) { + return tr("There is no \"rtp.ssrc\" field in this version of Wireshark."); + } + + /* Try to compile the filter. */ + if (!dfilter_compile(filter_text, &sfcode, &df_err)) { + QString err = QString(df_err->msg); + df_error_free(&df_err); + return err; + } + + if (!capture_file_.capFile() || !capture_file_.capFile()->current_frame) close(); + + if (!cf_read_current_record(capture_file_.capFile())) close(); + + frame_data *fdata = capture_file_.capFile()->current_frame; + + epan_dissect_t edt; + + epan_dissect_init(&edt, capture_file_.capFile()->epan, true, false); + epan_dissect_prime_with_dfilter(&edt, sfcode); + epan_dissect_prime_with_hfid(&edt, hfid_rtp_ssrc); + epan_dissect_run(&edt, capture_file_.capFile()->cd_t, + &capture_file_.capFile()->rec, + frame_tvbuff_new_buffer( + &capture_file_.capFile()->provider, fdata, + &capture_file_.capFile()->buf), + fdata, NULL); + + /* + * Packet must be an RTPv2 packet with an SSRC; we use the filter to + * check. + */ + if (!dfilter_apply_edt(sfcode, &edt)) { + epan_dissect_cleanup(&edt); + dfilter_free(sfcode); + return tr("Please select an RTPv2 packet with an SSRC value"); + } + + dfilter_free(sfcode); + + if (!reverse) { + // If we only want streams that match the SSRC in this frame, we + // can just allocate an RTP stream ID directly instead of having + // to redissect all the other packets. + + /* We need the SSRC value of the current frame; try to get it. */ + GPtrArray *gp = proto_get_finfo_ptr_array(edt.tree, hfid_rtp_ssrc); + if (gp == NULL || gp->len == 0) { + /* XXX - should not happen, as the filter includes rtp.ssrc */ + epan_dissect_cleanup(&edt); + return tr("SSRC value not found."); + } + + /* + * OK, we have the SSRC value(s), so we can proceed. + * (Try to handle the unlikely case of a frame with more than one + * SSRC; perhaps a DVD-S2 Baseband frame? Does that even work + * properly?) + */ + for (unsigned i = 0; i < gp->len; i++) { + new_id = g_new0(rtpstream_id_t, 1); + rtpstream_id_copy_pinfo(&(edt.pi), new_id, false); + new_id->ssrc = fvalue_get_uinteger(((field_info *)gp->pdata[i])->value); + *stream_ids << new_id; + } + } else { + // If we want to find all SSRCs with the same RTP session as this + // frame, then we have to redissect all packets. + + /* Register the tap listener */ + memset(&tapinfo, 0, sizeof(rtpstream_tapinfo_t)); + tapinfo.tap_data = this; + tapinfo.mode = TAP_ANALYSE; + + /* Scan for RTP streams (redissect all packets) */ + rtpstream_scan(&tapinfo, capture_file_.capFile(), Q_NULLPTR); + + for (GList *strinfo_list = g_list_first(tapinfo.strinfo_list); strinfo_list; strinfo_list = gxx_list_next(strinfo_list)) { + rtpstream_info_t * strinfo = gxx_list_data(rtpstream_info_t*, strinfo_list); + // We want any RTP stream ID that matches the address and ports. + // This could mean more than one in the forward direction, if + // e.g., BUNDLE is used (RFC 9143). + if (rtpstream_id_equal_pinfo(&(strinfo->id), &(edt.pi), false) || + rtpstream_id_equal_pinfo(&(strinfo->id), &(edt.pi), true)) { + new_id = g_new0(rtpstream_id_t, 1); + rtpstream_id_copy(&(strinfo->id), new_id); + *stream_ids << new_id; + } + } + rtpstream_reset_cb(&tapinfo); + } + + epan_dissect_cleanup(&edt); + + return NULL; +} + +void WiresharkMainWindow::openTLSKeylogDialog() +{ + // Have a single instance of the dialog at any one time. + if (!tlskeylog_dialog_) { + tlskeylog_dialog_ = new TLSKeylogDialog(*this); + tlskeylog_dialog_->setAttribute(Qt::WA_DeleteOnClose); + } + + if (tlskeylog_dialog_->isMinimized()) { + tlskeylog_dialog_->showNormal(); + } + else { + tlskeylog_dialog_->show(); + } + tlskeylog_dialog_->raise(); + tlskeylog_dialog_->activateWindow(); +} |