2 * Routines for dynamic protocol help menus
6 * Edgar Gladkich <edgar.gladkich@incacon.de>
7 * Gerald Combs <gerald@wireshark.org>
9 * Wireshark - Network traffic analyzer
10 * By Gerald Combs <gerald@wireshark.org>
11 * Copyright 1998 Gerald Combs
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
29 /* This file basically does nothing except remind us how this worked before
30 * the menu code was rewritten to no longer use the deprecated ItemFactory
33 #define MAIN_MENU_USE_UIMANAGER 1
38 #include "ui/gtk/webbrowser.h"
42 #include <epan/prefs.h>
43 #include <epan/filesystem.h>
44 #include <epan/strutil.h>
45 #include <epan/proto.h>
47 #include "ui/gtk/proto_help.h"
50 * The protocol help files cannot be downloaded downloaded directly. You
51 * will need to go to http://www.inacon.com/download/, then download one
52 * of the packages, e.g. the source package and extract the protocol_help
53 * folder. To actually use the help you will also need to be able to
54 * access the URL beginning at the contents of the loclation line in the
55 * ph.ini file (e.g. location=http://www.inacon.de/ph/data/).
56 * To provide offline service download the contents of the given location
57 * to your computer, then change the location parameter in the ph.ini file
58 * to point to that new URL. This may be a file:/// URL.
61 /* Right now proto_help will not build with -DGTK_DISABLE_DEPRECATED due to
62 its use of GtkItemFactory.
63 See http://developer.gnome.org/gtk/2.24/GtkItemFactory.html: "As of GTK+ 2.4,
64 GtkItemFactory has been deprecated in favour of GtkUIManager."
65 This needs to be rewritten by the people who added this code or some other
66 volunteers. Otherwise this functionality will be lost once the non UI_MANAGER
67 stuff gets removed it isn't built by default any more.
69 #ifdef MAIN_MENU_USE_UIMANAGER
70 void proto_help_menu_modify(GtkTreeSelection
*selection _U_
, capture_file
*cf _U_
) {}
71 void proto_help_menu_init(GtkWidget
*widget _U_
) {}
72 void proto_help_init(void) {}
75 #define PH_MENU_TOP "/Protocol Help"
77 #define PH_FILE_LOG "ph.log"
78 #define PH_INI_SUFFIX ".ini"
79 #define PH_PATH_SEARCH_STR "${PATH}"
81 /* .ini Section names */
82 #define PH_INI_GROUP_DATABASE "database"
83 #define PH_INI_GROUP_LOCATION_DATA "location data"
84 #define PH_INI_GROUP_MAP "map"
86 /* .ini [database] section keys */
87 #define PH_INI_DB_KEY_SOURCE "source"
88 #define PH_INI_DB_KEY_LOCATION_TEMPLATE "location"
90 /* .ini Path sections */
91 #define PH_INI_KEY_OVERVIEW "_OVERVIEW"
93 /* Where to look for .ini files */
94 #define PH_CONFFILE_SUBDIR "protocol_help"
97 const gchar
*ph_log_path
;
100 typedef struct proto_help_key_file_t
{
103 const gchar
*loc_template
;
104 } proto_help_key_file
;
106 GPtrArray
*g_ph_key_files
= NULL
;
108 GtkItemFactory
*g_ph_menu_factory
;
110 static void ph_menu_reset(void);
111 static void ph_menu_onclick(GtkWidget
*, gpointer
, guint
);
113 static int ph_capture_get_protocol_id(GtkTreeSelection
*, capture_file
*);
114 static const gchar
* ph_capture_get_protocol_name(GtkTreeSelection
*, capture_file
*);
115 static const gchar
* ph_capture_get_protocol_abbrev(GtkTreeSelection
*, capture_file
*);
116 static gchar
* ph_capture_get_description(capture_file
*);
118 static proto_help_key_file
*ph_ini_load_file(const gchar
*);
119 static gchar
* ph_ini_get_value(GKeyFile
*, const gchar
*, const gchar
*, gchar
*);
120 static gchar
* ph_ini_get_path(GKeyFile
*, const gchar
*, const gchar
*);
121 static gchar
** ph_ini_get_keywords(GKeyFile
*, const gchar
*);
123 static guint
ph_parse_string(const gchar
*, const gchar
*);
125 /* #define PH_DEBUG_LOG 1 */
127 static void ph_logging_handler(const gchar
*, GLogLevelFlags
, const gchar
*, gpointer
);
131 * Protocol help routines. Adds web browser links to protocol menu items
132 * via configuration files.
140 #define PH_CONF_DIRS 2
141 void proto_help_init(void)
143 gchar
*search_dir
[PH_CONF_DIRS
];
144 const gchar
*ini_name
;
149 search_dir
[0] = g_strdup_printf("%s" G_DIR_SEPARATOR_S PH_CONFFILE_SUBDIR
, get_datafile_dir());
150 /* XXX - Use profiles? */
151 search_dir
[1] = get_persconffile_path(PH_CONFFILE_SUBDIR
, FALSE
, FALSE
);
154 g_log_set_handler(NULL
, G_LOG_LEVEL_MASK
| G_LOG_FLAG_FATAL
| G_LOG_FLAG_RECURSION
, ph_logging_handler
, NULL
);
160 g_ph_key_files
= g_ptr_array_new();
165 ph_log_path
= g_strdup_printf("%s" G_DIR_SEPARATOR_S
"%s", g_get_tmp_dir(), PH_FILE_LOG
);
168 for (i
= 0; i
< PH_CONF_DIRS
; i
++) {
169 g_log(NULL
, G_LOG_LEVEL_INFO
, "Looking for protocol help files in '%s'", search_dir
[i
]);
170 conf_dir
= g_dir_open(search_dir
[i
], 0, NULL
);
175 while ((ini_name
= g_dir_read_name(conf_dir
)) != NULL
) {
176 if (! g_str_has_suffix(ini_name
, PH_INI_SUFFIX
)) {
179 g_log(NULL
, G_LOG_LEVEL_INFO
, "-- Found '%s'", ini_name
);
180 ini_path
= g_strdup_printf("%s" G_DIR_SEPARATOR_S
"%s", search_dir
[i
], ini_name
);
181 ph_ini_load_file(ini_path
);
184 g_dir_close(conf_dir
);
188 /** Initialize the menu
190 * @param widget Context menu root
193 void proto_help_menu_init(GtkWidget
*widget
)
195 g_ph_menu_factory
= gtk_item_factory_from_widget(widget
);
204 static void ph_menu_reset(void)
206 GtkWidget
*menu_item
= NULL
;
207 GList
*menu_entries
= NULL
;
208 GList
*menu_entry
= NULL
;
210 if(!g_ph_menu_factory
) return;
212 menu_item
= gtk_item_factory_get_widget(g_ph_menu_factory
, PH_MENU_TOP
);
213 menu_entries
= gtk_container_get_children(GTK_CONTAINER(menu_item
));
215 for(menu_entry
= g_list_first(menu_entries
); menu_entry
!= NULL
; menu_entry
= g_list_next(menu_entry
))
217 gtk_container_remove(GTK_CONTAINER(menu_item
), menu_entry
->data
);
220 menu_item
= gtk_item_factory_get_item(g_ph_menu_factory
, PH_MENU_TOP
);
221 gtk_widget_set_sensitive(menu_item
, FALSE
);
224 /* Callback to free URLs */
226 url_destroy_cb(GtkWidget
*w _U_
, gpointer url
) {
230 /** Fill in the protocol help menu
232 * @param selection Currently-selected packet
233 * @param cf Capture file
236 void proto_help_menu_modify(GtkTreeSelection
*selection
, capture_file
*cf
)
239 const gchar
*proto_abbrev
, *proto_name
;
244 GtkWidget
*menu_item
= NULL
;
245 GtkItemFactoryEntry
*menu_entry
= NULL
;
246 proto_help_key_file
* phkf
;
248 gboolean add_separator
= FALSE
;
249 gboolean found
= FALSE
;
251 if(!g_ph_menu_factory
) return;
254 proto_abbrev
= ph_capture_get_protocol_abbrev(selection
, cf
);
255 if(!proto_abbrev
) return;
257 proto_name
= ph_capture_get_protocol_name(selection
, cf
);
258 if(!proto_name
) return;
260 description
= ph_capture_get_description(cf
);
262 for (cur_kf
= 0; cur_kf
< g_ph_key_files
->len
; cur_kf
++) {
263 phkf
= (proto_help_key_file
*) g_ptr_array_index(g_ph_key_files
, cur_kf
);
266 value
= ph_ini_get_path(phkf
->keyfile
, proto_abbrev
, PH_INI_KEY_OVERVIEW
);
270 g_log(NULL
, G_LOG_LEVEL_DEBUG
, "Overview page of the protocol '%s' is not defined", proto_abbrev
);
275 * XXX - We could save some memory here if we stored the location template
276 * and search value as separate items. However, that makes freeing them
279 loc
= string_replace(phkf
->loc_template
, PH_PATH_SEARCH_STR
, value
);
281 if (!loc
|| !strlen(loc
)) continue;
284 menu_entry
= g_malloc0(sizeof(GtkItemFactoryEntry
));
285 menu_entry
->path
= g_strdup_printf("%s/<separator>", PH_MENU_TOP
);
286 menu_entry
->item_type
= "<Separator>";
287 gtk_item_factory_create_item(g_ph_menu_factory
, menu_entry
, NULL
, 2);
289 add_separator
= TRUE
;
291 menu_entry
= g_malloc0(sizeof(GtkItemFactoryEntry
));
292 menu_entry
->path
= g_strdup_printf("%s/%s %s Overview", PH_MENU_TOP
, phkf
->source
, proto_name
);
293 menu_entry
->callback
= ph_menu_onclick
;
294 gtk_item_factory_create_item(g_ph_menu_factory
, menu_entry
, loc
, 2);
295 menu_item
= gtk_item_factory_get_widget(g_ph_menu_factory
, menu_entry
->path
);
297 g_signal_connect(menu_item
, "destroy", G_CALLBACK(url_destroy_cb
), loc
);
303 keys
= ph_ini_get_keywords(phkf
->keyfile
, proto_abbrev
);
304 table
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);
306 for(i
= 0; keys
[i
] != NULL
; i
++)
308 if(!strcmp(keys
[i
], PH_INI_KEY_OVERVIEW
)) continue; /* We already added the overview */
309 if(!ph_parse_string(description
, keys
[i
])) continue; /* Bad format */
310 if(g_hash_table_lookup(table
, g_ascii_strup(keys
[i
], -1)) != NULL
) continue; /* Duplicate */
312 value
= ph_ini_get_path(phkf
->keyfile
, proto_abbrev
, keys
[i
]);
313 if(!value
|| !strlen(value
)) continue;
315 loc
= string_replace(phkf
->loc_template
, PH_PATH_SEARCH_STR
, value
);
317 if (!loc
|| !strlen(loc
)) continue;
319 g_hash_table_insert(table
, g_ascii_strup(keys
[i
], -1), GINT_TO_POINTER(1));
321 menu_entry
= g_malloc0(sizeof(GtkItemFactoryEntry
));
322 menu_entry
->path
= g_strdup_printf("%s/%s", PH_MENU_TOP
, keys
[i
]);
323 menu_entry
->callback
= ph_menu_onclick
;
324 gtk_item_factory_create_item(g_ph_menu_factory
, menu_entry
, loc
, 2);
325 menu_item
= gtk_item_factory_get_widget(g_ph_menu_factory
, menu_entry
->path
);
327 g_signal_connect(menu_item
, "destroy", G_CALLBACK(url_destroy_cb
), loc
);
330 g_hash_table_destroy(table
);
335 menu_item
= gtk_item_factory_get_item(g_ph_menu_factory
, PH_MENU_TOP
);
336 gtk_widget_set_sensitive(menu_item
, found
);
340 * Function ph_menu_onclick
342 * @param GtkWidget *widget Description
343 * @param gpointer data Description
344 * @param guint action Description
347 static void ph_menu_onclick(GtkWidget
*widget _U_
, gpointer data
, guint action _U_
)
349 gchar
*loc
= (gchar
*) data
;
351 g_log(NULL
, G_LOG_LEVEL_DEBUG
, "Sending '%s' to the browser.", loc
);
354 g_log(NULL
, G_LOG_LEVEL_DEBUG
, "Protocol help ended up with a NULL URL.");
358 /* XXX - Should we do any RFC 3986 escaping first? */
359 if (! browser_open_url(loc
)) {
360 g_log(NULL
, G_LOG_LEVEL_DEBUG
, "Couldn't get protocol help for %s", loc
);
364 /** Get the field ID for a selected tree item.
366 * @param selection Tree selection
367 * @param cfile Capture file
368 * @return Field ID or 0
371 ph_capture_get_protocol_id(GtkTreeSelection
*selection
, capture_file
*cf
)
373 GtkTreeModel
*model
= NULL
;
376 field_info
*finfo
= NULL
;
379 if(!cf
->finfo_selected
) return 0;
380 proto_id
= cf
->finfo_selected
->hfinfo
->id
;
384 if(!gtk_tree_selection_get_selected(GTK_TREE_SELECTION(selection
), &model
, &iter
)) return 0;
386 while(gtk_tree_model_iter_parent(model
, &parent
, &iter
))
388 gtk_tree_model_get(model
, &parent
, 1, &finfo
, -1);
391 if(finfo
->hfinfo
->id
> 0)
393 proto_id
= finfo
->hfinfo
->id
;
399 while(proto_id
&& !proto_registrar_is_protocol(proto_id
))
401 proto_id
= proto_registrar_get_parent(proto_id
);
407 /** Get the protocol name for a selected tree item.
409 * @param selection Tree selection
410 * @param cfile Capture file
411 * @return Name for a protocol or NULL
414 ph_capture_get_protocol_name(GtkTreeSelection
*selection
, capture_file
*cf
)
416 int proto_id
= ph_capture_get_protocol_id(selection
, cf
);
418 return (!proto_id
) ? NULL
: proto_get_protocol_short_name(find_protocol_by_id(proto_id
));
421 /** Get the abbreviated protocol name for a selected tree item.
423 * @param selection Tree selection
424 * @param cfile Capture file
425 * @return Abbreviated (lower-case) name for a protocol or NULL
428 ph_capture_get_protocol_abbrev(GtkTreeSelection
*selection
, capture_file
*cf
)
430 int proto_id
= ph_capture_get_protocol_id(selection
, cf
);
432 return (!proto_id
) ? NULL
: proto_registrar_get_abbrev(proto_id
);
435 /** Return the selected item description
436 * @param cf capture file pointer
437 * @return Description of the selected item. MUST be freed with g_free().
439 static gchar
* ph_capture_get_description(capture_file
*cf
)
441 gchar
*buffer
= NULL
;
443 if(cf
->finfo_selected
->rep
&&
444 strlen(cf
->finfo_selected
->rep
->representation
) > 0)
446 buffer
= g_strdup(cf
->finfo_selected
->rep
->representation
);
450 buffer
= g_malloc(ITEM_LABEL_LENGTH
);
451 proto_item_fill_label(cf
->finfo_selected
, buffer
);
457 /** Load a protocol help key file and add it to the global array.
459 * @param filename Full path to the key file.
460 * @return Newly-created key file entry or NULL.
462 static proto_help_key_file
*
463 ph_ini_load_file(const gchar
*filename
)
466 GError
*error
= NULL
;
467 proto_help_key_file
*phkf
= NULL
;
468 gchar
*old_template
, *loc_template
;
469 gchar
**loc_data
, *loc_repl
, *loc_search
;
472 if(!g_file_test(filename
, G_FILE_TEST_EXISTS
))
474 g_log(NULL
, G_LOG_LEVEL_DEBUG
, "Configuration file %s does not exists", filename
);
478 kf
= g_key_file_new();
480 if(!g_key_file_load_from_file(kf
, filename
, G_KEY_FILE_NONE
, &error
))
482 g_log(NULL
, G_LOG_LEVEL_DEBUG
, "Configuration file '%s' could not be loaded (%s)", filename
, error
->message
);
489 loc_template
= ph_ini_get_value(kf
, PH_INI_GROUP_DATABASE
, PH_INI_DB_KEY_LOCATION_TEMPLATE
, NULL
);
495 loc_data
= g_key_file_get_keys(kf
, PH_INI_GROUP_LOCATION_DATA
, &len
, NULL
);
497 for (i
= 0; i
< len
; i
++) {
498 loc_repl
= ph_ini_get_value(kf
, PH_INI_GROUP_LOCATION_DATA
, loc_data
[i
], NULL
);
499 old_template
= loc_template
;
500 loc_search
= g_strdup_printf("${%s}", loc_data
[i
]);
501 loc_template
= string_replace(loc_template
, loc_search
, loc_repl
);
504 g_free(old_template
);
508 /* Add ${PATH} to the end if it's not present */
509 if (!strstr(loc_template
, PH_PATH_SEARCH_STR
)) {
510 old_template
= loc_template
;
511 loc_template
= g_strdup_printf("%s" PH_PATH_SEARCH_STR
, old_template
);
512 g_free(old_template
);
515 phkf
= g_malloc(sizeof(proto_help_key_file
));
517 phkf
->source
= ph_ini_get_value(kf
, PH_INI_GROUP_DATABASE
, PH_INI_DB_KEY_SOURCE
, "(Unknown source)");
518 phkf
->loc_template
= loc_template
;
520 g_ptr_array_add(g_ph_key_files
, phkf
);
525 /** Fetch a value for a key from a key file with an optional default value.
527 * @param keyfile The key file to search
528 * @param group Key file group
529 * @param key Key file value to fetch
530 * @param alt Alternate string to return. May be NULL.
531 * @return const gchar* A newly-allocated key value, or a copy of alt if not found.
534 ph_ini_get_value(GKeyFile
*keyfile
, const gchar
*group
, const gchar
*key
, gchar
*alt
)
539 value
= g_key_file_get_string(keyfile
, group
, key
, NULL
);
543 value
= g_strdup(alt
);
549 /** Given a protocol name and a key, map the protocol name to a section in the
550 * keyfile, then look up the value for that key.
552 * @param keyfile The key file to search
553 * @param protocol Wireshark protocol name to map, e.g. "tcp".
554 * @param keyword The key to fetch from the mapped section.
558 ph_ini_get_path(GKeyFile
*keyfile
, const gchar
*protocol
, const gchar
*keyword
)
560 GError
*error
= NULL
;
564 if(!keyfile
|| !protocol
|| !keyword
) return NULL
;
566 map
= g_key_file_get_string(keyfile
, PH_INI_GROUP_MAP
, protocol
, &error
);
570 g_log(NULL
, G_LOG_LEVEL_DEBUG
, "Protocol '%s' is not defined (%s)", protocol
, error
->message
);
575 value
= g_key_file_get_string(keyfile
, map
, keyword
, NULL
);
580 /** Given a protocol name, map it to a section in the keyfile, then
581 * return the keys in that section.
583 * @param keyfile The key file to search
584 * @param protocol Wireshark protocol name to map, e.g. "tcp".
585 * @return An array of keys in the mapped section. Must be freed with g_strfreev().
588 ph_ini_get_keywords(GKeyFile
*keyfile
, const gchar
*protocol
)
590 GError
*error
= NULL
;
595 if(!keyfile
) return NULL
;
597 map
= g_key_file_get_string(keyfile
, PH_INI_GROUP_MAP
, protocol
, &error
);
601 g_log(NULL
, G_LOG_LEVEL_DEBUG
, "Protocol '%s' is not defined (%s)", protocol
, error
->message
);
607 keys
= g_key_file_get_keys(keyfile
, map
, &length
, &error
);
612 g_log(NULL
, G_LOG_LEVEL_DEBUG
, "Display titles are not defined (%s)", protocol
);
620 * Function ph_parse_string
622 * @param const gchar *description Description
623 * @param const gchar *value Description
624 * @return guint Description
626 static guint
ph_parse_string(const gchar
*description
, const gchar
*value
)
628 GRegex
*regex
= NULL
;
629 GMatchInfo
*match_info
= NULL
;
630 gchar
*pattern
= NULL
;
633 pattern
= g_strdup_printf("(?<![0-9a-zA-Z_])%s(?![0-9a-zA-Z_])", value
);
634 regex
= g_regex_new(pattern
, 0, 0, NULL
);
636 g_regex_match(regex
, description
, 0, &match_info
);
637 if(g_match_info_matches(match_info
)) result
= 1;
639 g_match_info_free(match_info
);
640 g_regex_unref(regex
);
647 * Function ph_logging_handler
649 * @param const gchar *domain Description
650 * @param GLogLevelFlags level Description
651 * @param const gchar *message Description
652 * @param gpointer data Description
655 static void ph_logging_handler(const gchar
*domain _U_
, GLogLevelFlags level
, const gchar
*message
, gpointer data _U_
)
660 struct tm
*timestamp
= NULL
;
664 timestamp
= localtime(&now
);
666 switch(level
& G_LOG_LEVEL_MASK
)
668 case G_LOG_LEVEL_ERROR
:
672 case G_LOG_LEVEL_DEBUG
:
676 case G_LOG_LEVEL_INFO
:
684 file
= ws_fopen(ph_log_path
, "a+");
688 log
= g_strdup_printf("[%04u-%02u-%02u %02u:%02u:%02u %s] %s\n", timestamp
->tm_year
+ 1900, timestamp
->tm_mon
+ 1, timestamp
->tm_mday
, timestamp
->tm_hour
, timestamp
->tm_min
, timestamp
->tm_sec
, type
, message
);
693 #endif /* PH_DEBUG_LOG */
694 #endif /* MAIN_MENU_USE_UIMANAGER */