forked from osmocom/wireshark
WSLUA: Add new lua function register_packet_menu()
This adds support to Wireshark for custom context menus for packets, so that when a packet's context menu is opened (e.g., by right-clicking), Wireshark can support doing things like "run a program" or "open a URL" with a field from the packet as a parameter. Note that this is similar to ArcSight's integration commands feature. For example, it could be used like the following: ``` ROBTEX_URL = "https://www.robtex.com/dns-lookup/" local function search_robtex(...) local fields = {...}; for i, field in ipairs( fields ) do if (field.name == 'http.host') then browser_open_url(ROBTEX_URL .. field.value) break end end end register_packet_menu("Search host in Robtex", search_robtex, "http.host"); ``` Fixes issue #14998
This commit is contained in:
parent
eff7cd15b0
commit
f413260df9
128
epan/funnel.c
128
epan/funnel.c
|
@ -34,6 +34,36 @@ static funnel_menu_t* added_menus = NULL;
|
|||
static funnel_menu_t* removed_menus = NULL;
|
||||
static gboolean menus_registered = FALSE;
|
||||
|
||||
/**
|
||||
* Represents a single packet menu entry and callback
|
||||
*/
|
||||
typedef struct _funnel_packet_menu_t {
|
||||
char *name; /**< Name to display in the GUI */
|
||||
char *required_fields; /**< comma-separated list of fields
|
||||
that must be present for the
|
||||
packet menu to be displayed */
|
||||
funnel_packet_menu_callback callback; /**< Lua function to be called on
|
||||
menu item selection. */
|
||||
gpointer callback_data; /**< Lua state for the callback
|
||||
function */
|
||||
gboolean retap; /**< Whether or not to rescan the
|
||||
capture file's packets */
|
||||
struct _funnel_packet_menu_t* next; /**< Pointer to the next
|
||||
_funnel_packet_menu_t for the
|
||||
singly-linked list
|
||||
implemenation */
|
||||
} funnel_packet_menu_t;
|
||||
|
||||
/*
|
||||
* List of all registered funnel_packet_menu_t's
|
||||
*/
|
||||
static funnel_packet_menu_t* registered_packet_menus = NULL;
|
||||
/*
|
||||
* TRUE if the packet menus were modified since the last registration
|
||||
*/
|
||||
static gboolean packet_menus_modified = FALSE;
|
||||
static void funnel_clear_packet_menu (funnel_packet_menu_t** menu_list);
|
||||
|
||||
const funnel_ops_t* funnel_get_funnel_ops(void) { return ops; }
|
||||
void funnel_set_funnel_ops(const funnel_ops_t* o) { ops = o; }
|
||||
|
||||
|
@ -120,6 +150,10 @@ void funnel_deregister_menus(funnel_menu_callback callback)
|
|||
|
||||
funnel_remove_menu(®istered_menus, m);
|
||||
funnel_insert_menu(&removed_menus, m);
|
||||
|
||||
// Clear and free memory of packet menus
|
||||
funnel_clear_packet_menu(®istered_packet_menus);
|
||||
packet_menus_modified = TRUE;
|
||||
}
|
||||
|
||||
void funnel_register_all_menus(funnel_registration_cb_t r_cb)
|
||||
|
@ -146,9 +180,103 @@ void funnel_reload_menus(funnel_deregistration_cb_t d_cb,
|
|||
funnel_clear_menu(&added_menus);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Inserts a funnel_packet_menu_t into a list of funnel_packet_menu_t's
|
||||
*
|
||||
* @param menu_list the list of menus that the menu will be added to
|
||||
* @param menu the menu to add to the list of menus
|
||||
*/
|
||||
static void funnel_insert_packet_menu (funnel_packet_menu_t** menu_list, funnel_packet_menu_t *menu)
|
||||
{
|
||||
if (!(*menu_list)) {
|
||||
*menu_list = menu;
|
||||
} else {
|
||||
funnel_packet_menu_t* c;
|
||||
for (c = *menu_list; c->next; c = c->next);
|
||||
c->next = menu;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for Lua code to register a packet menu
|
||||
*
|
||||
* Stores the menu name and callback from the Lua code
|
||||
* into registered_packet_menus so that the
|
||||
* Wireshark GUI code can retrieve it with
|
||||
* funnel_register_all_packet_menus().
|
||||
*/
|
||||
void funnel_register_packet_menu(const char *name,
|
||||
const char *required_fields,
|
||||
funnel_packet_menu_callback callback,
|
||||
gpointer callback_data,
|
||||
gboolean retap)
|
||||
{
|
||||
funnel_packet_menu_t* m = g_new0(funnel_packet_menu_t, 1);
|
||||
m->name = g_strdup(name);
|
||||
m->required_fields = g_strdup(required_fields);
|
||||
m->callback = callback;
|
||||
m->callback_data = callback_data;
|
||||
m->retap = retap;
|
||||
m->next = NULL;
|
||||
|
||||
funnel_insert_packet_menu(®istered_packet_menus, m);
|
||||
packet_menus_modified = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears a list of funnel_packet_menu_t's and free()s all associated memory
|
||||
*
|
||||
* @param menu_list the list of menus to clear
|
||||
*/
|
||||
static void funnel_clear_packet_menu (funnel_packet_menu_t** menu_list)
|
||||
{
|
||||
funnel_packet_menu_t *m;
|
||||
|
||||
while (*menu_list) {
|
||||
m = *menu_list;
|
||||
*menu_list = m->next;
|
||||
g_free(m->name);
|
||||
g_free(m->required_fields);
|
||||
if (m->callback_data) {
|
||||
g_free(m->callback_data);
|
||||
}
|
||||
g_free(m);
|
||||
}
|
||||
*menu_list = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for Wireshark GUI to obtain all registered packet menus
|
||||
*
|
||||
* Calls the supplied callback for each packet menu registered with
|
||||
* funnel_register_packet_menu().
|
||||
*
|
||||
* @param r_cb the callback function to call with each registered packet menu
|
||||
*/
|
||||
void funnel_register_all_packet_menus(funnel_registration_packet_cb_t r_cb)
|
||||
{
|
||||
funnel_packet_menu_t* c;
|
||||
for (c = registered_packet_menus; c; c = c->next) {
|
||||
r_cb(c->name,c->required_fields,c->callback,c->callback_data,c->retap);
|
||||
}
|
||||
packet_menus_modified = FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_packet_menus_modified(void)
|
||||
{
|
||||
return packet_menus_modified;
|
||||
}
|
||||
|
||||
void funnel_cleanup(void)
|
||||
{
|
||||
funnel_clear_menu(®istered_menus);
|
||||
funnel_clear_packet_menu(®istered_packet_menus);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -118,6 +118,49 @@ WS_DLL_PUBLIC void funnel_reload_menus(funnel_deregistration_cb_t d_cb,
|
|||
funnel_registration_cb_t r_cb);
|
||||
WS_DLL_PUBLIC void funnel_cleanup(void);
|
||||
|
||||
/**
|
||||
* Signature of function that can be called from a custom packet menu entry
|
||||
*/
|
||||
typedef void (* funnel_packet_menu_callback)(gpointer, GPtrArray*);
|
||||
|
||||
/**
|
||||
* Signature of callback function to register packet menu entries
|
||||
*/
|
||||
typedef void (*funnel_registration_packet_cb_t)(const char *name,
|
||||
const char *required_fields,
|
||||
funnel_packet_menu_callback callback,
|
||||
gpointer callback_data,
|
||||
gboolean retap);
|
||||
|
||||
/**
|
||||
* Entry point for Wireshark GUI to obtain all registered packet menus
|
||||
*
|
||||
* @param r_cb function which will be called to register each packet menu entry
|
||||
*/
|
||||
WS_DLL_PUBLIC void funnel_register_all_packet_menus(funnel_registration_packet_cb_t r_cb);
|
||||
|
||||
/**
|
||||
* Entry point for Lua code to register a packet menu
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
WS_DLL_PUBLIC void funnel_register_packet_menu(const char *name,
|
||||
const char *required_fields,
|
||||
funnel_packet_menu_callback callback,
|
||||
gpointer callback_data,
|
||||
gboolean retap);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
WS_DLL_PUBLIC gboolean funnel_packet_menus_modified(void);
|
||||
|
||||
extern void initialize_funnel_ops(void);
|
||||
|
||||
extern void funnel_dump_all_text_windows(void);
|
||||
|
|
|
@ -137,6 +137,93 @@ void wslua_deregister_menus(void) {
|
|||
funnel_deregister_menus(lua_menu_callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error handler used by lua_custom_packet_menu_callback when calling the user-supplied callback
|
||||
*
|
||||
* @param L State of the Lua interpreter
|
||||
* @return Always returns 0
|
||||
*/
|
||||
static int packet_menu_cb_error_handler(lua_State* L) {
|
||||
const gchar* error = lua_tostring(L,1);
|
||||
report_failure("Lua: Error During execution of Packet Menu Callback:\n %s",error);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper used to call the user-supplied Lua callback when a custom packet
|
||||
* context menu is clicked.
|
||||
*
|
||||
* @param data Lua menu data
|
||||
* @param finfo_array packet data
|
||||
*/
|
||||
static void lua_custom_packet_menu_callback(gpointer data, GPtrArray *finfo_array) {
|
||||
// _lua_menu_data is State + the integer index of a callback.
|
||||
struct _lua_menu_data* md = (struct _lua_menu_data *)data;
|
||||
lua_State* L = md->L;
|
||||
|
||||
lua_settop(L,0);
|
||||
lua_pushcfunction(L,packet_menu_cb_error_handler);
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, md->cb_ref);
|
||||
|
||||
// Push the packet data as arguments to the Lua callback:
|
||||
int items_found = 0;
|
||||
for (guint i = finfo_array->len - 1; i > 0 ; i --) {
|
||||
field_info *fi = (field_info *)g_ptr_array_index (finfo_array, i);
|
||||
push_FieldInfo(L, fi);
|
||||
items_found++;
|
||||
}
|
||||
|
||||
switch ( lua_pcall(L,items_found,0,1) ) {
|
||||
case 0:
|
||||
break;
|
||||
case LUA_ERRRUN:
|
||||
g_warning("Runtime error while calling custom_packet_menu callback");
|
||||
break;
|
||||
case LUA_ERRMEM:
|
||||
g_warning("Memory alloc error while calling custom_packet_menu callback");
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lua function exposed to users: register_packet_menu
|
||||
*/
|
||||
WSLUA_FUNCTION wslua_register_packet_menu(lua_State* L) { /* Register a menu item in the packet list. */
|
||||
#define WSLUA_ARG_register_packet_menu_NAME 1 /* The name of the menu item. Use slashes to separate submenus. (e.g. level1/level2/name). (string) */
|
||||
#define WSLUA_ARG_register_packet_menu_ACTION 2 /* The function to be called when the menu item is invoked. The function must take one argument and return nothing. */
|
||||
#define WSLUA_OPTARG_register_packet_menu_REQUIRED_FIELDS 3 /* A comma-separated list of packet fields (e.g., http.host,dns.qry.name) which all must be present for the menu to be displayed (default: always display)*/
|
||||
|
||||
const gchar* name = luaL_checkstring(L,WSLUA_ARG_register_packet_menu_NAME);
|
||||
const gchar* required_fields = luaL_optstring(L,WSLUA_OPTARG_register_packet_menu_REQUIRED_FIELDS,"");
|
||||
|
||||
struct _lua_menu_data* md;
|
||||
gboolean retap = FALSE;
|
||||
|
||||
if (!lua_isfunction(L,WSLUA_ARG_register_packet_menu_ACTION)) {
|
||||
WSLUA_ARG_ERROR(register_packet_menu,ACTION,"Must be a function");
|
||||
return 0;
|
||||
}
|
||||
|
||||
md = g_new0(struct _lua_menu_data, 1);
|
||||
md->L = L;
|
||||
|
||||
lua_pushvalue(L, 2);
|
||||
md->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
lua_remove(L,2);
|
||||
|
||||
funnel_register_packet_menu(name,
|
||||
required_fields,
|
||||
lua_custom_packet_menu_callback,
|
||||
md,
|
||||
retap);
|
||||
WSLUA_RETURN(0);
|
||||
}
|
||||
|
||||
struct _dlg_cb_data {
|
||||
lua_State* L;
|
||||
int func_ref;
|
||||
|
|
|
@ -748,8 +748,11 @@ libwireshark.so.0 libwireshark0 #MINVER#
|
|||
ftype_pretty_name@Base 1.9.1
|
||||
funnel_cleanup@Base 2.9.1
|
||||
funnel_get_funnel_ops@Base 1.9.1
|
||||
funnel_packet_menus_modified@Base 4.1.0
|
||||
funnel_register_all_menus@Base 1.9.1
|
||||
funnel_register_all_packet_menus@Base 4.1.0
|
||||
funnel_register_menu@Base 1.9.1
|
||||
funnel_register_packet_menu@Base 4.1.0
|
||||
funnel_reload_menus@Base 1.99.9
|
||||
funnel_set_funnel_ops@Base 1.9.1
|
||||
fvalue_from_literal@Base 3.7.0
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "ui/progress_dlg.h"
|
||||
#include "ui/simple_dialog.h"
|
||||
#include <ui/qt/main_window.h>
|
||||
|
||||
#include "funnel_statistics.h"
|
||||
#include "funnel_string_dialog.h"
|
||||
|
@ -30,6 +31,7 @@
|
|||
#include <QClipboard>
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
#include <QMenu>
|
||||
#include <QUrl>
|
||||
|
||||
#include "main_application.h"
|
||||
|
@ -61,48 +63,138 @@ static void progress_window_update(struct progdlg *progress_dialog, float percen
|
|||
static void progress_window_destroy(struct progdlg *progress_dialog);
|
||||
}
|
||||
|
||||
class FunnelAction : public QAction
|
||||
{
|
||||
public:
|
||||
FunnelAction(QString title, funnel_menu_callback callback, gpointer callback_data, gboolean retap, QObject *parent = nullptr) :
|
||||
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<QString>();
|
||||
}
|
||||
|
||||
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<QString>())
|
||||
{
|
||||
// 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())
|
||||
{
|
||||
// Use "&&" to get a real ampersand in the menu item.
|
||||
title.replace('&', "&&");
|
||||
|
||||
setText(title);
|
||||
setObjectName(FunnelStatistics::actionName());
|
||||
setText(menuComponents.last());
|
||||
menuComponents.removeLast();
|
||||
packetSubmenu_ = menuComponents.join("/");
|
||||
}
|
||||
|
||||
funnel_menu_callback callback() const {
|
||||
return callback_;
|
||||
}
|
||||
setObjectName(FunnelStatistics::actionName());
|
||||
setPacketRequiredFields(packet_required_fields);
|
||||
}
|
||||
|
||||
QString title() const {
|
||||
return title_;
|
||||
}
|
||||
FunnelAction::~FunnelAction(){
|
||||
}
|
||||
|
||||
void triggerCallback() {
|
||||
if (callback_) {
|
||||
callback_(callback_data_);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool retap() {
|
||||
if (retap_) return true;
|
||||
return false;
|
||||
const QSet<QString> FunnelAction::getPacketRequiredFields() {
|
||||
return packetRequiredFields_;
|
||||
}
|
||||
|
||||
|
||||
void FunnelAction::setPacketData(GPtrArray* finfos) {
|
||||
packetData_ = finfos;
|
||||
}
|
||||
|
||||
void FunnelAction::addToMenu(QMenu * ctx_menu, QHash<QString, QMenu *> 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);
|
||||
}
|
||||
|
||||
private:
|
||||
QString title_;
|
||||
funnel_menu_callback callback_;
|
||||
gpointer callback_data_;
|
||||
gboolean retap_;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
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<int, QList<FunnelAction *> > funnel_actions_;
|
||||
const QString FunnelStatistics::action_name_ = "FunnelStatisticsAction";
|
||||
|
@ -368,6 +460,33 @@ static void register_menu_cb(const char *name,
|
|||
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<MainWindow *>(mainApp->mainWindow());
|
||||
if (mainwindow) {
|
||||
mainwindow->appendPacketMenu(funnel_action);
|
||||
}
|
||||
}
|
||||
|
||||
static void deregister_menu_cb(funnel_menu_callback callback)
|
||||
{
|
||||
foreach (int group, funnel_actions_.keys()) {
|
||||
|
@ -397,6 +516,29 @@ 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"
|
||||
|
|
|
@ -11,13 +11,21 @@
|
|||
#define FUNNELSTATISTICS_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QAction>
|
||||
#include <QSet>
|
||||
|
||||
#include <epan/funnel.h>
|
||||
#include "capture_file.h"
|
||||
#include <ui/qt/filter_action.h>
|
||||
|
||||
struct _funnel_ops_t;
|
||||
struct progdlg;
|
||||
|
||||
/**
|
||||
* Signature of function that can be called from a custom packet menu entry
|
||||
*/
|
||||
typedef void (* funnel_packet_menu_callback)(gpointer, GPtrArray*);
|
||||
|
||||
class FunnelStatistics : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -53,8 +61,42 @@ private:
|
|||
QString prepared_filter_;
|
||||
};
|
||||
|
||||
class FunnelAction : public QAction
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
FunnelAction(QString title, funnel_menu_callback callback, gpointer callback_data, gboolean retap, QObject *parent);
|
||||
FunnelAction(QString title, funnel_packet_menu_callback callback, gpointer callback_data, gboolean retap, const char *packet_required_fields, QObject *parent);
|
||||
~FunnelAction();
|
||||
funnel_menu_callback callback() const;
|
||||
QString title() const;
|
||||
void triggerCallback();
|
||||
void setPacketCallback(funnel_packet_menu_callback packet_callback);
|
||||
void setPacketData(GPtrArray* finfos);
|
||||
void addToMenu(QMenu * ctx_menu, QHash<QString, QMenu *> menuTextToMenus);
|
||||
void setPacketRequiredFields(const char *required_fields_str);
|
||||
const QSet<QString> getPacketRequiredFields();
|
||||
bool retap();
|
||||
QString getPacketSubmenus();
|
||||
|
||||
public slots:
|
||||
void triggerPacketCallback();
|
||||
|
||||
private:
|
||||
QString title_;
|
||||
QString packetSubmenu_;
|
||||
funnel_menu_callback callback_;
|
||||
gpointer callback_data_;
|
||||
gboolean retap_;
|
||||
funnel_packet_menu_callback packetCallback_;
|
||||
GPtrArray* packetData_;
|
||||
QSet<QString> packetRequiredFields_;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
void funnel_statistics_reload_menus(void);
|
||||
void funnel_statistics_load_packet_menus(void);
|
||||
gboolean funnel_statistics_packet_menus_modified(void);
|
||||
} // extern "C"
|
||||
|
||||
#endif // FUNNELSTATISTICS_H
|
||||
|
|
|
@ -15,9 +15,13 @@
|
|||
|
||||
#include "main_window.h"
|
||||
|
||||
#include "funnel_statistics.h"
|
||||
#include "packet_list.h"
|
||||
#include "widgets/display_filter_combo.h"
|
||||
|
||||
// Packet Menu actions
|
||||
static QList<QAction *> dynamic_packet_menu_actions;
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
main_stack_(nullptr),
|
||||
|
@ -33,6 +37,11 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
clearAddedPacketMenus();
|
||||
}
|
||||
|
||||
bool MainWindow::hasSelection()
|
||||
{
|
||||
if (packet_list_)
|
||||
|
@ -89,4 +98,96 @@ void MainWindow::setDisplayFilter(QString filter, FilterAction::Action action, F
|
|||
emit filterAction(filter, action, filterType);
|
||||
}
|
||||
|
||||
/*
|
||||
* Used for registering custom packet menus
|
||||
*
|
||||
* @param funnel_action a custom packet menu action
|
||||
*/
|
||||
void MainWindow::appendPacketMenu(QAction* funnel_action)
|
||||
{
|
||||
dynamic_packet_menu_actions.append(funnel_action);
|
||||
connect(funnel_action, SIGNAL(triggered(bool)), funnel_action, SLOT(triggerPacketCallback()));
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the list of registered packet menu actions
|
||||
*
|
||||
* After ensuring that all stored custom packet menu actions
|
||||
* are registered with the Wireshark GUI, it returns them as a list
|
||||
* so that they can potentially be displayed to a user.
|
||||
*
|
||||
* @return the list of registered packet menu actions
|
||||
*/
|
||||
QList<QAction *> MainWindow::getPacketMenuActions()
|
||||
{
|
||||
if (funnel_statistics_packet_menus_modified()) {
|
||||
// If the packet menus were modified, we need to clear the already
|
||||
// loaded packet menus to avoid duplicates
|
||||
this->clearAddedPacketMenus();
|
||||
funnel_statistics_load_packet_menus();
|
||||
}
|
||||
return dynamic_packet_menu_actions;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clears the list of registered packet menu actions
|
||||
*
|
||||
* Clears the list of registered packet menu actions
|
||||
* and frees all associated memory.
|
||||
*/
|
||||
void MainWindow::clearAddedPacketMenus()
|
||||
{
|
||||
for( int i=0; i<dynamic_packet_menu_actions.count(); ++i )
|
||||
{
|
||||
delete dynamic_packet_menu_actions[i];
|
||||
}
|
||||
dynamic_packet_menu_actions.clear();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Adds the custom packet menus to the supplied QMenu
|
||||
*
|
||||
* This method takes in QMenu and the selected packet's data
|
||||
* and adds all applicable custom packet menus to it.
|
||||
*
|
||||
* @param ctx_menu The menu to add the packet menu entries to
|
||||
* @param finfo_array The data in the selected packet
|
||||
* @return true if a packet menu was added to the ctx_menu
|
||||
*/
|
||||
bool MainWindow::addPacketMenus(QMenu * ctx_menu, GPtrArray *finfo_array)
|
||||
{
|
||||
bool insertedPacketMenu = false;
|
||||
QList<QAction *> myPacketMenuActions = this->getPacketMenuActions();
|
||||
if (myPacketMenuActions.isEmpty()) {
|
||||
return insertedPacketMenu;
|
||||
}
|
||||
|
||||
// Build a set of fields present for efficient lookups
|
||||
QSet<QString> fieldsPresent = QSet<QString>();
|
||||
for (guint fieldInfoIndex = 0; fieldInfoIndex < finfo_array->len; fieldInfoIndex++) {
|
||||
field_info *fi = (field_info *)g_ptr_array_index (finfo_array, fieldInfoIndex);
|
||||
fieldsPresent.insert(QString(fi->hfinfo->abbrev));
|
||||
}
|
||||
|
||||
// Place actions in the relevant (sub)menu
|
||||
// The 'root' menu is the ctx_menu, so map NULL to that
|
||||
QHash<QString, QMenu *> menuTextToMenus;
|
||||
menuTextToMenus.insert(NULL, ctx_menu);
|
||||
foreach (QAction * action, myPacketMenuActions) {
|
||||
if (! qobject_cast<FunnelAction *>(action)) {
|
||||
continue;
|
||||
}
|
||||
FunnelAction * packetAction = qobject_cast<FunnelAction *>(action);
|
||||
|
||||
// Only display a menu if all required fields are present
|
||||
if (!fieldsPresent.contains(packetAction->getPacketRequiredFields())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
packetAction->setPacketData(finfo_array);
|
||||
packetAction->addToMenu(ctx_menu, menuTextToMenus);
|
||||
insertedPacketMenu = true;
|
||||
}
|
||||
return insertedPacketMenu;
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ class MainWindow : public QMainWindow
|
|||
Q_OBJECT
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
bool hasSelection();
|
||||
QList<int> selectedRows(bool useFrameNum = false);
|
||||
void insertColumn(QString name, QString abbrev, gint pos = -1);
|
||||
|
@ -50,6 +51,12 @@ public:
|
|||
QString getFilter();
|
||||
MainStatusBar *statusBar();
|
||||
|
||||
// Used for managing custom packet menus
|
||||
void appendPacketMenu(QAction* funnel_action);
|
||||
QList<QAction*> getPacketMenuActions();
|
||||
void clearAddedPacketMenus();
|
||||
bool addPacketMenus(QMenu * ctx_menu, GPtrArray *finfo_array);
|
||||
|
||||
public slots:
|
||||
void setDisplayFilter(QString filter, FilterAction::Action action, FilterAction::ActionType filterType);
|
||||
virtual void filterPackets(QString, bool) = 0;
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
#include <ui/qt/widgets/drag_label.h>
|
||||
#include <ui/qt/filter_action.h>
|
||||
#include <ui/qt/decode_as_dialog.h>
|
||||
#include <ui/qt/wireshark_main_window.h>
|
||||
|
||||
#include <QAction>
|
||||
#include <QActionGroup>
|
||||
|
@ -206,7 +207,8 @@ PacketList::PacketList(QWidget *parent) :
|
|||
set_column_visibility_(false),
|
||||
frozen_rows_(QModelIndexList()),
|
||||
cur_history_(-1),
|
||||
in_history_(false)
|
||||
in_history_(false),
|
||||
finfo_array(NULL)
|
||||
{
|
||||
setItemsExpandable(false);
|
||||
setRootIsDecorated(false);
|
||||
|
@ -263,6 +265,14 @@ PacketList::PacketList(QWidget *parent) :
|
|||
connect(verticalScrollBar(), SIGNAL(actionTriggered(int)), this, SLOT(vScrollBarActionTriggered(int)));
|
||||
}
|
||||
|
||||
PacketList::~PacketList()
|
||||
{
|
||||
if (finfo_array)
|
||||
{
|
||||
g_ptr_array_free(finfo_array, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
void PacketList::colorsChanged()
|
||||
{
|
||||
const QString c_active = "active";
|
||||
|
@ -569,8 +579,13 @@ void PacketList::contextMenuEvent(QContextMenuEvent *event)
|
|||
|
||||
proto_prefs_menus_.clear();
|
||||
|
||||
if (finfo_array)
|
||||
{
|
||||
g_ptr_array_free(finfo_array, TRUE);
|
||||
finfo_array = NULL;
|
||||
}
|
||||
if (cap_file_ && cap_file_->edt && cap_file_->edt->tree) {
|
||||
GPtrArray *finfo_array = proto_all_finfos(cap_file_->edt->tree);
|
||||
finfo_array = proto_all_finfos(cap_file_->edt->tree);
|
||||
QList<QString> added_proto_prefs;
|
||||
|
||||
for (guint i = 0; i < finfo_array->len; i++) {
|
||||
|
@ -599,7 +614,6 @@ void PacketList::contextMenuEvent(QContextMenuEvent *event)
|
|||
added_proto_prefs << module_name;
|
||||
}
|
||||
}
|
||||
g_ptr_array_free(finfo_array, TRUE);
|
||||
}
|
||||
|
||||
QModelIndex ctxIndex = indexAt(event->pos());
|
||||
|
@ -623,6 +637,15 @@ void PacketList::contextMenuEvent(QContextMenuEvent *event)
|
|||
|
||||
ctx_menu->addSeparator();
|
||||
|
||||
// Code for custom context menus from Lua's register_packet_menu()
|
||||
MainWindow * mainWindow = qobject_cast<MainWindow *>(mainApp->mainWindow());
|
||||
if (cap_file_ && cap_file_->edt && cap_file_->edt->tree && mainWindow) {
|
||||
bool insertedPacketMenu = mainWindow->addPacketMenus(ctx_menu, finfo_array);
|
||||
if (insertedPacketMenu) {
|
||||
ctx_menu->addSeparator();
|
||||
}
|
||||
}
|
||||
|
||||
ctx_menu->addAction(window()->findChild<QAction *>("actionViewEditResolvedName"));
|
||||
ctx_menu->addSeparator();
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ class PacketList : public QTreeView
|
|||
Q_OBJECT
|
||||
public:
|
||||
explicit PacketList(QWidget *parent = 0);
|
||||
~PacketList();
|
||||
|
||||
enum SummaryCopyType {
|
||||
CopyAsText,
|
||||
|
@ -142,6 +143,7 @@ private:
|
|||
QVector<int> selection_history_;
|
||||
int cur_history_;
|
||||
bool in_history_;
|
||||
GPtrArray *finfo_array; // Packet data from the last selected packet entry
|
||||
|
||||
void setFrameReftime(gboolean set, frame_data *fdata);
|
||||
void setColumnVisibility();
|
||||
|
|
|
@ -1547,6 +1547,7 @@ void WiresharkMainWindow::reloadLuaPlugins()
|
|||
mainApp->setReloadingLua(true);
|
||||
|
||||
wslua_reload_plugins(NULL, NULL);
|
||||
this->clearAddedPacketMenus();
|
||||
funnel_statistics_reload_menus();
|
||||
reloadDynamicMenus();
|
||||
closePacketDialogs();
|
||||
|
|
Loading…
Reference in New Issue