/* funnel_statistics.cpp * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "config.h" #include #include "epan/color_filters.h" #include "file.h" #include "epan/funnel.h" #include "epan/prefs.h" #include #include "ui/progress_dlg.h" #include "ui/simple_dialog.h" #include #include "funnel_statistics.h" #include "funnel_string_dialog.h" #include "funnel_text_dialog.h" #include #include #include #include #include #include #include "main_application.h" // To do: // - Handle menu paths. Do we create a new path (GTK+) or use the base element? // - Add a FunnelGraphDialog class? extern "C" { static struct _funnel_text_window_t* text_window_new(funnel_ops_id_t *ops_id, const char* title); static void string_dialog_new(funnel_ops_id_t *ops_id, const gchar* title, const gchar** field_names, const gchar** field_values, funnel_dlg_cb_t dialog_cb, void* dialog_cb_data, funnel_dlg_cb_data_free_t dialog_cb_data_free); static void funnel_statistics_logger(const gchar *, enum ws_log_level, const gchar *message, gpointer); static void funnel_statistics_retap_packets(funnel_ops_id_t *ops_id); static void funnel_statistics_copy_to_clipboard(GString *text); static const gchar *funnel_statistics_get_filter(funnel_ops_id_t *ops_id); static void funnel_statistics_set_filter(funnel_ops_id_t *ops_id, const char* filter_string); static gchar* funnel_statistics_get_color_filter_slot(guint8 filter_num); static void funnel_statistics_set_color_filter_slot(guint8 filter_num, const gchar* filter_string); static gboolean funnel_statistics_open_file(funnel_ops_id_t *ops_id, const char* fname, const char* filter, char**); static void funnel_statistics_reload_packets(funnel_ops_id_t *ops_id); static void funnel_statistics_redissect_packets(funnel_ops_id_t *ops_id); static void funnel_statistics_reload_lua_plugins(funnel_ops_id_t *ops_id); static void funnel_statistics_apply_filter(funnel_ops_id_t *ops_id); static gboolean browser_open_url(const gchar *url); static void browser_open_data_file(const gchar *filename); static struct progdlg *progress_window_new(funnel_ops_id_t *ops_id, const gchar* title, const gchar* task, gboolean terminate_is_stop, gboolean *stop_flag); static void progress_window_update(struct progdlg *progress_dialog, float percentage, const gchar* status); static void progress_window_destroy(struct progdlg *progress_dialog); } FunnelAction::FunnelAction(QString title, funnel_menu_callback callback, gpointer callback_data, gboolean retap, QObject *parent = nullptr) : QAction(parent), title_(title), callback_(callback), callback_data_(callback_data), retap_(retap) { // Use "&&" to get a real ampersand in the menu item. title.replace('&', "&&"); setText(title); setObjectName(FunnelStatistics::actionName()); packetRequiredFields_ = QSet(); } FunnelAction::FunnelAction(QString title, funnel_packet_menu_callback callback, gpointer callback_data, gboolean retap, const char *packet_required_fields, QObject *parent = nullptr) : QAction(parent), title_(title), callback_data_(callback_data), retap_(retap), packetCallback_(callback), packetRequiredFields_(QSet()) { // Use "&&" to get a real ampersand in the menu item. title.replace('&', "&&"); QStringList menuComponents = title.split(QString("/")); // Set the menu's text to the rightmost component, set the path to being everything to the left: setText("(empty)"); packetSubmenu_ = ""; if (!menuComponents.isEmpty()) { setText(menuComponents.last()); menuComponents.removeLast(); packetSubmenu_ = menuComponents.join("/"); } setObjectName(FunnelStatistics::actionName()); setPacketRequiredFields(packet_required_fields); } FunnelAction::~FunnelAction(){ } funnel_menu_callback FunnelAction::callback() const { return callback_; } QString FunnelAction::title() const { return title_; } void FunnelAction::triggerCallback() { if (callback_) { callback_(callback_data_); } } void FunnelAction::setPacketCallback(funnel_packet_menu_callback packet_callback) { packetCallback_ = packet_callback; } void FunnelAction::setPacketRequiredFields(const char *required_fields_str) { packetRequiredFields_.clear(); // If multiple fields are required to be present, they're split by commas // Also remove leading and trailing spaces, in case someone writes // "http, dns" instead of "http,dns" QString requiredFieldsJoined = QString(required_fields_str); #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) QStringList requiredFieldsSplit = requiredFieldsJoined.split(",", Qt::SkipEmptyParts); #else QStringList requiredFieldsSplit = requiredFieldsJoined.split(",", QString::SkipEmptyParts); #endif foreach (QString requiredField, requiredFieldsSplit) { QString trimmedFieldName = requiredField.trimmed(); if (! trimmedFieldName.isEmpty()) { packetRequiredFields_.insert(trimmedFieldName); } } } const QSet FunnelAction::getPacketRequiredFields() { return packetRequiredFields_; } void FunnelAction::setPacketData(GPtrArray* finfos) { packetData_ = finfos; } void FunnelAction::addToMenu(QMenu * ctx_menu, QHash menuTextToMenus) { QString submenusText = this->getPacketSubmenus(); if (submenusText.isEmpty()) { ctx_menu->addAction(this); } else { // If the action has a submenu, ensure that the // the full submenu chain exists: QStringList menuComponents = submenusText.split("/"); QString menuSubComponentsStringPrior = NULL; for (int menuIndex=0; menuIndex < menuComponents.size(); menuIndex++) { QStringList menuSubComponents = menuComponents.mid(0, menuIndex+1); QString menuSubComponentsString = menuSubComponents.join("/"); if (!menuTextToMenus.contains(menuSubComponentsString)) { // Create a new menu object under the prior object QMenu *previousSubmenu = menuTextToMenus.value(menuSubComponentsStringPrior); QMenu *submenu = previousSubmenu->addMenu(menuComponents.at(menuIndex)); menuTextToMenus.insert(menuSubComponentsString, submenu); } menuSubComponentsStringPrior = menuSubComponentsString; } // Then add the action to the relevant submenu QMenu *parentMenu = menuTextToMenus.value(submenusText); parentMenu->addAction(this); } } void FunnelAction::triggerPacketCallback() { if (packetCallback_) { packetCallback_(callback_data_, packetData_); } } bool FunnelAction::retap() { if (retap_) return true; return false; } QString FunnelAction::getPacketSubmenus() { return packetSubmenu_; } static QHash > funnel_actions_; const QString FunnelStatistics::action_name_ = "FunnelStatisticsAction"; static gboolean menus_registered = FALSE; struct _funnel_ops_id_t { FunnelStatistics *funnel_statistics; }; FunnelStatistics::FunnelStatistics(QObject *parent, CaptureFile &cf) : QObject(parent), capture_file_(cf), prepared_filter_(QString()) { funnel_ops_ = new(struct _funnel_ops_t); memset(funnel_ops_, 0, sizeof(struct _funnel_ops_t)); funnel_ops_id_ = new(struct _funnel_ops_id_t); funnel_ops_id_->funnel_statistics = this; funnel_ops_->ops_id = funnel_ops_id_; funnel_ops_->new_text_window = text_window_new; funnel_ops_->set_text = text_window_set_text; funnel_ops_->append_text = text_window_append; funnel_ops_->prepend_text = text_window_prepend; funnel_ops_->clear_text = text_window_clear; funnel_ops_->get_text = text_window_get_text; funnel_ops_->set_close_cb = text_window_set_close_cb; funnel_ops_->set_editable = text_window_set_editable; funnel_ops_->destroy_text_window = text_window_destroy; funnel_ops_->add_button = text_window_add_button; funnel_ops_->new_dialog = string_dialog_new; funnel_ops_->close_dialogs = string_dialogs_close; funnel_ops_->logger = funnel_statistics_logger; funnel_ops_->retap_packets = funnel_statistics_retap_packets; funnel_ops_->copy_to_clipboard = funnel_statistics_copy_to_clipboard; funnel_ops_->get_filter = funnel_statistics_get_filter; funnel_ops_->set_filter = funnel_statistics_set_filter; funnel_ops_->get_color_filter_slot = funnel_statistics_get_color_filter_slot; funnel_ops_->set_color_filter_slot = funnel_statistics_set_color_filter_slot; funnel_ops_->open_file = funnel_statistics_open_file; funnel_ops_->reload_packets = funnel_statistics_reload_packets; funnel_ops_->redissect_packets = funnel_statistics_redissect_packets; funnel_ops_->reload_lua_plugins = funnel_statistics_reload_lua_plugins; funnel_ops_->apply_filter = funnel_statistics_apply_filter; funnel_ops_->browser_open_url = browser_open_url; funnel_ops_->browser_open_data_file = browser_open_data_file; funnel_ops_->new_progress_window = progress_window_new; funnel_ops_->update_progress = progress_window_update; funnel_ops_->destroy_progress_window = progress_window_destroy; funnel_set_funnel_ops(funnel_ops_); } FunnelStatistics::~FunnelStatistics() { // At this point we're probably closing the program and will shortly // call epan_cleanup, which calls ProgDlg__gc and TextWindow__gc. // They in turn depend on funnel_ops_ being valid. memset(funnel_ops_id_, 0, sizeof(struct _funnel_ops_id_t)); memset(funnel_ops_, 0, sizeof(struct _funnel_ops_t)); // delete(funnel_ops_id_); // delete(funnel_ops_); } void FunnelStatistics::retapPackets() { capture_file_.retapPackets(); } struct progdlg *FunnelStatistics::progressDialogNew(const gchar *task_title, const gchar *item_title, gboolean terminate_is_stop, gboolean *stop_flag) { return create_progress_dlg(parent(), task_title, item_title, terminate_is_stop, stop_flag); } const char *FunnelStatistics::displayFilter() { return display_filter_.constData(); } void FunnelStatistics::emitSetDisplayFilter(const QString filter) { prepared_filter_ = filter; emit setDisplayFilter(filter, FilterAction::ActionPrepare, FilterAction::ActionTypePlain); } void FunnelStatistics::reloadPackets() { capture_file_.reload(); } void FunnelStatistics::redissectPackets() { // This will trigger a packet redissection. mainApp->emitAppSignal(MainApplication::PacketDissectionChanged); } void FunnelStatistics::reloadLuaPlugins() { mainApp->reloadLuaPluginsDelayed(); } void FunnelStatistics::emitApplyDisplayFilter() { emit setDisplayFilter(prepared_filter_, FilterAction::ActionApply, FilterAction::ActionTypePlain); } void FunnelStatistics::emitOpenCaptureFile(QString cf_path, QString filter) { emit openCaptureFile(cf_path, filter); } void FunnelStatistics::funnelActionTriggered() { FunnelAction *funnel_action = dynamic_cast(sender()); if (!funnel_action) return; funnel_action->triggerCallback(); } void FunnelStatistics::displayFilterTextChanged(const QString &filter) { display_filter_ = filter.toUtf8(); } struct _funnel_text_window_t* text_window_new(funnel_ops_id_t *ops_id, const char* title) { return FunnelTextDialog::textWindowNew(qobject_cast(ops_id->funnel_statistics->parent()), title); } void string_dialog_new(funnel_ops_id_t *ops_id, const gchar* title, const gchar** field_names, const gchar** field_values, funnel_dlg_cb_t dialog_cb, void* dialog_cb_data, funnel_dlg_cb_data_free_t dialog_cb_data_free) { QList> field_list; for (int i = 0; field_names[i]; i++) { QPair field = QPair(QString(field_names[i]), QString("")); if (field_values != NULL && field_values[i]) { field.second = QString(field_values[i]); } field_list << field; } FunnelStringDialog::stringDialogNew(qobject_cast(ops_id->funnel_statistics->parent()), title, field_list, dialog_cb, dialog_cb_data, dialog_cb_data_free); } void funnel_statistics_logger(const gchar *log_domain, enum ws_log_level log_level, const gchar *message, gpointer) { ws_log(log_domain, log_level, "%s", message); } void funnel_statistics_retap_packets(funnel_ops_id_t *ops_id) { if (!ops_id || !ops_id->funnel_statistics) return; ops_id->funnel_statistics->retapPackets(); } void funnel_statistics_copy_to_clipboard(GString *text) { mainApp->clipboard()->setText(text->str); } const gchar *funnel_statistics_get_filter(funnel_ops_id_t *ops_id) { if (!ops_id || !ops_id->funnel_statistics) return nullptr; return ops_id->funnel_statistics->displayFilter(); } void funnel_statistics_set_filter(funnel_ops_id_t *ops_id, const char* filter_string) { if (!ops_id || !ops_id->funnel_statistics) return; ops_id->funnel_statistics->emitSetDisplayFilter(filter_string); } gchar* funnel_statistics_get_color_filter_slot(guint8 filter_num) { return color_filters_get_tmp(filter_num); } void funnel_statistics_set_color_filter_slot(guint8 filter_num, const gchar* filter_string) { gchar *err_msg = nullptr; if (!color_filters_set_tmp(filter_num, filter_string, FALSE, &err_msg)) { simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", err_msg); g_free(err_msg); } } gboolean funnel_statistics_open_file(funnel_ops_id_t *ops_id, const char* fname, const char* filter, char**) { // XXX We need to return a proper error value. We should probably move // MainWindow::openCaptureFile to CaptureFile and add error handling // there. if (!ops_id || !ops_id->funnel_statistics) return FALSE; QString cf_name(fname); QString cf_filter(filter); ops_id->funnel_statistics->emitOpenCaptureFile(cf_name, cf_filter); return TRUE; } void funnel_statistics_reload_packets(funnel_ops_id_t *ops_id) { if (!ops_id || !ops_id->funnel_statistics) return; ops_id->funnel_statistics->reloadPackets(); } void funnel_statistics_redissect_packets(funnel_ops_id_t *ops_id) { if (!ops_id || !ops_id->funnel_statistics) return; ops_id->funnel_statistics->redissectPackets(); } void funnel_statistics_reload_lua_plugins(funnel_ops_id_t *ops_id) { if (!ops_id || !ops_id->funnel_statistics) return; ops_id->funnel_statistics->reloadLuaPlugins(); } void funnel_statistics_apply_filter(funnel_ops_id_t *ops_id) { if (!ops_id || !ops_id->funnel_statistics) return; ops_id->funnel_statistics->emitApplyDisplayFilter(); } gboolean browser_open_url(const gchar *url) { return QDesktopServices::openUrl(QUrl(url)) ? TRUE : FALSE; } void browser_open_data_file(const gchar *filename) { QDesktopServices::openUrl(QUrl::fromLocalFile(filename)); } struct progdlg *progress_window_new(funnel_ops_id_t *ops_id, const gchar* task_title, const gchar* item_title, gboolean terminate_is_stop, gboolean *stop_flag) { if (!ops_id || !ops_id->funnel_statistics) return nullptr; return ops_id->funnel_statistics->progressDialogNew(task_title, item_title, terminate_is_stop, stop_flag); } void progress_window_update(struct progdlg *progress_dialog, float percentage, const gchar* status) { update_progress_dlg(progress_dialog, percentage, status); } void progress_window_destroy(progdlg *progress_dialog) { destroy_progress_dlg(progress_dialog); } extern "C" { void register_tap_listener_qt_funnel(void); static void register_menu_cb(const char *name, register_stat_group_t group, funnel_menu_callback callback, gpointer callback_data, gboolean retap) { FunnelAction *funnel_action = new FunnelAction(name, callback, callback_data, retap, mainApp); if (menus_registered) { mainApp->appendDynamicMenuGroupItem(group, funnel_action); } else { mainApp->addDynamicMenuGroupItem(group, funnel_action); } if (!funnel_actions_.contains(group)) { funnel_actions_[group] = QList(); } funnel_actions_[group] << funnel_action; } /* * Callback used to register packet menus in the GUI. * * Creates a new FunnelAction with the Lua * callback and stores it in the Wireshark GUI with * appendPacketMenu() so it can be retrieved when * the packet's context menu is open. * * @param name packet menu item's name * @param required_fields fields required to be present for the packet menu to be displayed * @param callback function called when the menu item is invoked. The function must take one argument and return nothing. * @param callback_data Lua state for the callback function * @param retap whether or not to rescan all packets */ static void register_packet_menu_cb(const char *name, const char *required_fields, funnel_packet_menu_callback callback, gpointer callback_data, gboolean retap) { FunnelAction *funnel_action = new FunnelAction(name, callback, callback_data, retap, required_fields, mainApp); MainWindow * mainwindow = qobject_cast(mainApp->mainWindow()); if (mainwindow) { mainwindow->appendPacketMenu(funnel_action); } } static void deregister_menu_cb(funnel_menu_callback callback) { foreach (int group, funnel_actions_.keys()) { QList::iterator it = funnel_actions_[group].begin(); while (it != funnel_actions_[group].end()) { FunnelAction *funnel_action = *it; if (funnel_action->callback() == callback) { // Must set back to title to find the correct sub-menu in Tools funnel_action->setText(funnel_action->title()); mainApp->removeDynamicMenuGroupItem(group, funnel_action); it = funnel_actions_[group].erase(it); } else { ++it; } } } } void register_tap_listener_qt_funnel(void) { funnel_register_all_menus(register_menu_cb); menus_registered = TRUE; } void funnel_statistics_reload_menus(void) { funnel_reload_menus(deregister_menu_cb, register_menu_cb); funnel_statistics_load_packet_menus(); } /** * Returns whether the packet menus have been modified since they were last registered * * @return TRUE if the packet menus were modified since the last registration */ gboolean funnel_statistics_packet_menus_modified(void) { return funnel_packet_menus_modified(); } /* * Loads all registered_packet_menus into the * Wireshark GUI. */ void funnel_statistics_load_packet_menus(void) { funnel_register_all_packet_menus(register_packet_menu_cb); } } // extern "C"