5 * Ethereal - Network traffic analyzer
6 * By Gerald Combs <gerald@ethereal.com>
7 * Copyright 2000 Gerald Combs
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
42 #include "file_util.h"
46 #include "follow_dlg.h"
47 #include <epan/follow.h>
48 #include "dlg_utils.h"
52 #include "alert_box.h"
53 #include "simple_dialog.h"
54 #include <epan/dissectors/packet-ipv6.h>
55 #include <epan/prefs.h>
56 #include <epan/addr_resolv.h>
57 #include <epan/charsets.h>
59 #include "gui_utils.h"
60 #include <epan/epan_dissect.h>
61 #include <epan/filesystem.h>
62 #include "compat_macros.h"
63 #include <epan/ipproto.h>
64 #include "print_mswin.h"
65 #include "font_utils.h"
67 /* This is backwards-compatibility code for old versions of GTK+ (2.2.1 and
68 * earlier). It defines the new wrap behavior (unknown in earlier versions)
69 * as the old (slightly buggy) wrap behavior.
71 #ifndef GTK_WRAP_WORD_CHAR
72 #define GTK_WRAP_WORD_CHAR GTK_WRAP_WORD
92 show_stream_t show_stream;
93 show_type_t show_type;
94 char data_out_filename[128 + 1];
98 GtkWidget *hexdump_bt;
101 GtkWidget *follow_save_as_w;
103 char *filter_out_filter;
104 GtkWidget *filter_te;
105 GtkWidget *streamwindow;
108 static void follow_destroy_cb(GtkWidget * win, gpointer data);
109 static void follow_charset_toggle_cb(GtkWidget * w, gpointer parent_w);
110 static void follow_load_text(follow_info_t *follow_info);
111 static void follow_filter_out_stream(GtkWidget * w, gpointer parent_w);
112 static void follow_print_stream(GtkWidget * w, gpointer parent_w);
113 static void follow_save_as_cmd_cb(GtkWidget * w, gpointer data);
114 static void follow_save_as_ok_cb(GtkWidget * w, gpointer fs);
115 static void follow_save_as_destroy_cb(GtkWidget * win, gpointer user_data);
116 static void follow_stream_om_both(GtkWidget * w, gpointer data);
117 static void follow_stream_om_client(GtkWidget * w, gpointer data);
118 static void follow_stream_om_server(GtkWidget * w, gpointer data);
121 /* With MSVC and a libethereal.dll, we need a special declaration. */
122 ETH_VAR_IMPORT FILE *data_out_file;
124 #define E_FOLLOW_INFO_KEY "follow_info_key"
126 /* List of "follow_info_t" structures for all "Follow TCP Stream" windows,
127 so we can redraw them all if the colors or font changes. */
128 static GList *follow_infos;
130 /* Add a "follow_info_t" structure to the list. */
132 remember_follow_info(follow_info_t *follow_info)
134 follow_infos = g_list_append(follow_infos, follow_info);
137 /* Remove a "follow_info_t" structure from the list. */
139 forget_follow_info(follow_info_t *follow_info)
141 follow_infos = g_list_remove(follow_infos, follow_info);
145 follow_redraw(gpointer data, gpointer user_data _U_)
147 follow_load_text((follow_info_t *)data);
150 /* Redraw the text in all "Follow TCP Stream" windows. */
152 follow_redraw_all(void)
154 g_list_foreach(follow_infos, follow_redraw, NULL);
157 /* Follow the TCP stream, if any, to which the last packet that we called
158 a dissection routine on belongs (this might be the most recently
159 selected packet, or it might be the last packet in the file). */
161 follow_stream_cb(GtkWidget * w, gpointer data _U_)
163 GtkWidget *streamwindow, *vbox, *txt_scrollw, *text, *filter_te;
164 GtkWidget *hbox, *button_hbox, *button, *radio_bt;
165 GtkWidget *stream_fr, *stream_vb;
166 GtkWidget *stream_om, *stream_menu, *stream_mi;
167 GtkTooltips *tooltips;
169 gchar *follow_filter;
170 const gchar *previous_filter;
171 int filter_out_filter_len;
172 const char *hostname0, *hostname1;
175 follow_tcp_stats_t stats;
176 follow_info_t *follow_info;
178 /* we got tcp so we can follow */
179 if (cfile.edt->pi.ipproto != 6) {
180 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
181 "Error following stream. Please make\n"
182 "sure you have a TCP packet selected.");
186 follow_info = g_new0(follow_info_t, 1);
188 /* Create a temporary file into which to dump the reassembled data
189 from the TCP stream, and set "data_out_file" to refer to it, so
190 that the TCP code will write to it.
192 XXX - it might be nicer to just have the TCP code directly
193 append stuff to the text widget for the TCP stream window,
194 if we can arrange that said window not pop up until we're
196 tmp_fd = create_tempfile(follow_info->data_out_filename,
197 sizeof follow_info->data_out_filename, "follow");
200 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
201 "Could not create temporary file %s: %s",
202 follow_info->data_out_filename, strerror(errno));
207 data_out_file = fdopen(tmp_fd, "wb");
208 if (data_out_file == NULL) {
209 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
210 "Could not create temporary file %s: %s",
211 follow_info->data_out_filename, strerror(errno));
213 unlink(follow_info->data_out_filename);
218 /* Create a new filter that matches all packets in the TCP stream,
219 and set the display filter entry accordingly */
220 reset_tcp_reassembly();
221 follow_filter = build_follow_filter(&cfile.edt->pi);
223 /* Set the display filter entry accordingly */
224 filter_te = OBJECT_GET_DATA(w, E_DFILTER_TE_KEY);
226 /* needed in follow_filter_out_stream(), is there a better way? */
227 follow_info->filter_te = filter_te;
229 /* save previous filter, const since we're not supposed to alter */
231 (const gchar *)gtk_entry_get_text(GTK_ENTRY(filter_te));
233 /* allocate our new filter. API claims g_malloc terminates program on failure */
234 /* my calc for max alloc needed is really +10 but when did a few extra bytes hurt ? */
235 filter_out_filter_len = strlen(follow_filter) + strlen(previous_filter) + 16;
236 follow_info->filter_out_filter = (gchar *)g_malloc(filter_out_filter_len);
238 /* append the negation */
239 if(strlen(previous_filter)) {
240 g_snprintf(follow_info->filter_out_filter, filter_out_filter_len,
241 "%s and !(%s)", previous_filter, follow_filter);
243 g_snprintf(follow_info->filter_out_filter, filter_out_filter_len,
244 "!(%s)", follow_filter);
248 gtk_entry_set_text(GTK_ENTRY(filter_te), follow_filter);
250 /* Run the display filter so it goes in effect - even if it's the
251 same as the previous display filter. */
252 main_filter_packets(&cfile, follow_filter, TRUE);
254 /* Free the filter string, as we're done with it. */
255 g_free(follow_filter);
257 /* The data_out_file should now be full of the streams information */
258 fclose(data_out_file);
260 /* The data_out_filename file now has all the text that was in the session */
261 streamwindow = dlg_window_new("Follow TCP stream");
263 /* needed in follow_filter_out_stream(), is there a better way? */
264 follow_info->streamwindow = streamwindow;
266 gtk_widget_set_name(streamwindow, "TCP stream window");
267 gtk_window_set_default_size(GTK_WINDOW(streamwindow), DEF_WIDTH, DEF_HEIGHT);
268 gtk_container_border_width(GTK_CONTAINER(streamwindow), 6);
270 /* setup the container */
271 tooltips = gtk_tooltips_new ();
273 vbox = gtk_vbox_new(FALSE, 6);
274 gtk_container_add(GTK_CONTAINER(streamwindow), vbox);
277 if (incomplete_tcp_stream) {
278 stream_fr = gtk_frame_new("Stream Content (incomplete)");
280 stream_fr = gtk_frame_new("Stream Content");
282 gtk_container_add(GTK_CONTAINER(vbox), stream_fr);
283 gtk_widget_show(stream_fr);
285 stream_vb = gtk_vbox_new(FALSE, 6);
286 gtk_container_set_border_width( GTK_CONTAINER(stream_vb) , 6);
287 gtk_container_add(GTK_CONTAINER(stream_fr), stream_vb);
289 /* create a scrolled window for the text */
290 txt_scrollw = scrolled_window_new(NULL, NULL);
291 #if GTK_MAJOR_VERSION >= 2
292 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(txt_scrollw),
295 gtk_box_pack_start(GTK_BOX(stream_vb), txt_scrollw, TRUE, TRUE, 0);
297 /* create a text box */
298 #if GTK_MAJOR_VERSION < 2
299 text = gtk_text_new(NULL, NULL);
300 gtk_text_set_editable(GTK_TEXT(text), FALSE);
302 text = gtk_text_view_new();
303 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
304 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
306 gtk_container_add(GTK_CONTAINER(txt_scrollw), text);
307 follow_info->text = text;
311 hbox = gtk_hbox_new(FALSE, 1);
312 gtk_box_pack_start(GTK_BOX(stream_vb), hbox, FALSE, FALSE, 0);
314 /* Create Save As Button */
315 button = BUTTON_NEW_FROM_STOCK(GTK_STOCK_SAVE_AS);
316 SIGNAL_CONNECT(button, "clicked", follow_save_as_cmd_cb, follow_info);
317 gtk_tooltips_set_tip (tooltips, button, "Save the content as currently displayed ", NULL);
318 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
320 /* Create Print Button */
321 button = BUTTON_NEW_FROM_STOCK(GTK_STOCK_PRINT);
322 SIGNAL_CONNECT(button, "clicked", follow_print_stream, follow_info);
323 gtk_tooltips_set_tip (tooltips, button, "Print the content as currently displayed", NULL);
324 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
327 follow_tcp_stats(&stats);
330 struct e_in6_addr ipaddr;
331 memcpy(&ipaddr, stats.ip_address[0], 16);
332 hostname0 = get_hostname6(&ipaddr);
333 memcpy(&ipaddr, stats.ip_address[0], 16);
334 hostname1 = get_hostname6(&ipaddr);
337 memcpy(&ipaddr, stats.ip_address[0], 4);
338 hostname0 = get_hostname(ipaddr);
339 memcpy(&ipaddr, stats.ip_address[1], 4);
340 hostname1 = get_hostname(ipaddr);
343 port0 = get_tcp_port(stats.tcp_port[0]);
344 port1 = get_tcp_port(stats.tcp_port[1]);
346 follow_info->is_ipv6 = stats.is_ipv6;
348 stream_om = gtk_option_menu_new();
349 stream_menu = gtk_menu_new();
351 /* Both Stream Directions */
352 g_snprintf(string, sizeof(string),
353 "Entire conversation (%u bytes)",
354 stats.bytes_written[0] + stats.bytes_written[1]);
355 stream_mi = gtk_menu_item_new_with_label(string);
356 SIGNAL_CONNECT(stream_mi, "activate", follow_stream_om_both,
358 gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
359 gtk_widget_show(stream_mi);
360 follow_info->show_stream = BOTH_HOSTS;
362 /* Host 0 --> Host 1 */
363 g_snprintf(string, sizeof(string), "%s:%s --> %s:%s (%u bytes)",
364 hostname0, port0, hostname1, port1,
365 stats.bytes_written[0]);
366 stream_mi = gtk_menu_item_new_with_label(string);
367 SIGNAL_CONNECT(stream_mi, "activate", follow_stream_om_client,
369 gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
370 gtk_widget_show(stream_mi);
372 /* Host 1 --> Host 0 */
373 g_snprintf(string, sizeof(string), "%s:%s --> %s:%s (%u bytes)",
374 hostname1, port1, hostname0, port0,
375 stats.bytes_written[1]);
376 stream_mi = gtk_menu_item_new_with_label(string);
377 SIGNAL_CONNECT(stream_mi, "activate", follow_stream_om_server,
379 gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
380 gtk_widget_show(stream_mi);
382 gtk_option_menu_set_menu(GTK_OPTION_MENU(stream_om), stream_menu);
383 /* Set history to 0th item, i.e., the first item. */
384 gtk_option_menu_set_history(GTK_OPTION_MENU(stream_om), 0);
385 gtk_tooltips_set_tip (tooltips, stream_om,
386 "Select the stream direction to display", NULL);
387 gtk_box_pack_start(GTK_BOX(hbox), stream_om, FALSE, FALSE, 0);
389 /* ASCII radio button */
390 radio_bt = gtk_radio_button_new_with_label(NULL, "ASCII");
391 gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"ASCII\" format", NULL);
392 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), TRUE);
393 gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
394 SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
396 follow_info->ascii_bt = radio_bt;
397 follow_info->show_type = SHOW_ASCII;
399 /* EBCDIC radio button */
400 radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
401 (GTK_RADIO_BUTTON(radio_bt)),
403 gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"EBCDIC\" format", NULL);
404 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
405 gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
406 SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
408 follow_info->ebcdic_bt = radio_bt;
410 /* HEX DUMP radio button */
411 radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
412 (GTK_RADIO_BUTTON(radio_bt)),
414 gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"Hexdump\" format", NULL);
415 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
416 gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
417 SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
419 follow_info->hexdump_bt = radio_bt;
421 /* C Array radio button */
422 radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
423 (GTK_RADIO_BUTTON(radio_bt)),
425 gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"C Array\" format", NULL);
426 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
427 gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
428 SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
430 follow_info->carray_bt = radio_bt;
432 /* Raw radio button */
433 radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
434 (GTK_RADIO_BUTTON(radio_bt)),
436 gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"Raw\" (binary) format. "
437 "As this contains non printable characters, the screen output will be in ASCII format", NULL);
438 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
439 gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
440 SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
442 follow_info->raw_bt = radio_bt;
445 button_hbox = gtk_hbutton_box_new();
446 gtk_box_pack_start(GTK_BOX(vbox), button_hbox, FALSE, FALSE, 0);
447 gtk_button_box_set_layout (GTK_BUTTON_BOX(button_hbox), GTK_BUTTONBOX_END);
448 gtk_button_box_set_spacing(GTK_BUTTON_BOX(button_hbox), 5);
450 /* Create exclude stream button */
451 button = gtk_button_new_with_label("Filter out this stream");
452 SIGNAL_CONNECT(button, "clicked", follow_filter_out_stream, follow_info);
453 gtk_tooltips_set_tip (tooltips, button,
454 "Build a display filter which cuts this stream from the capture", NULL);
455 gtk_box_pack_start(GTK_BOX(button_hbox), button, FALSE, FALSE, 0);
457 /* Create Close Button */
458 button = BUTTON_NEW_FROM_STOCK(GTK_STOCK_CLOSE);
459 gtk_tooltips_set_tip (tooltips, button,
460 "Close the dialog and keep the current display filter", NULL);
461 gtk_box_pack_start(GTK_BOX(button_hbox), button, FALSE, FALSE, 0);
462 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
464 window_set_cancel_button(streamwindow, button, window_cancel_button_cb);
466 /* Tuck away the follow_info object into the window */
467 OBJECT_SET_DATA(streamwindow, E_FOLLOW_INFO_KEY, follow_info);
469 follow_load_text(follow_info);
470 remember_follow_info(follow_info);
472 data_out_file = NULL;
474 SIGNAL_CONNECT(streamwindow, "delete_event", window_delete_event_cb, NULL);
475 SIGNAL_CONNECT(streamwindow, "destroy", follow_destroy_cb, NULL);
477 /* Make sure this widget gets destroyed if we quit the main loop,
478 so that if we exit, we clean up any temporary files we have
479 for "Follow TCP Stream" windows. */
480 gtk_quit_add_destroy(gtk_main_level(), GTK_OBJECT(streamwindow));
482 gtk_widget_show_all(streamwindow);
483 window_present(streamwindow);
486 /* The destroy call back has the responsibility of
487 * unlinking the temporary file
488 * and freeing the filter_out_filter */
490 follow_destroy_cb(GtkWidget *w, gpointer data _U_)
492 follow_info_t *follow_info;
495 follow_info = OBJECT_GET_DATA(w, E_FOLLOW_INFO_KEY);
496 i = unlink(follow_info->data_out_filename);
498 g_warning("Follow: Couldn't remove temporary file: \"%s\", errno: %s (%u)",
499 follow_info->data_out_filename, strerror(errno), errno);
501 g_free(follow_info->filter_out_filter);
502 forget_follow_info(follow_info);
506 /* XXX - can I emulate follow_charset_toggle_cb() instead of having
507 * 3 different functions here?
508 * That might not be a bad idea, as it might mean we only reload
509 * the window once, not twice - see follow_charset_toggle_cb()
510 * for an explanation. */
512 follow_stream_om_both(GtkWidget *w _U_, gpointer data)
514 follow_info_t *follow_info = data;
515 follow_info->show_stream = BOTH_HOSTS;
516 follow_load_text(follow_info);
520 follow_stream_om_client(GtkWidget *w _U_, gpointer data)
522 follow_info_t *follow_info = data;
523 follow_info->show_stream = FROM_CLIENT;
524 follow_load_text(follow_info);
528 follow_stream_om_server(GtkWidget *w _U_, gpointer data)
530 follow_info_t *follow_info = data;
531 follow_info->show_stream = FROM_SERVER;
532 follow_load_text(follow_info);
536 /* Handles the display style toggling */
538 follow_charset_toggle_cb(GtkWidget * w _U_, gpointer data)
540 follow_info_t *follow_info = data;
543 * A radio button toggles when it goes on and when it goes
544 * off, so when you click a radio button two signals are
545 * delivered. We only want to reprocess the display once,
546 * so we do it only when the button goes on.
548 if (GTK_TOGGLE_BUTTON(w)->active) {
549 if (w == follow_info->ebcdic_bt)
550 follow_info->show_type = SHOW_EBCDIC;
551 else if (w == follow_info->hexdump_bt)
552 follow_info->show_type = SHOW_HEXDUMP;
553 else if (w == follow_info->carray_bt)
554 follow_info->show_type = SHOW_CARRAY;
555 else if (w == follow_info->ascii_bt)
556 follow_info->show_type = SHOW_ASCII;
557 else if (w == follow_info->raw_bt)
558 follow_info->show_type = SHOW_RAW;
559 follow_load_text(follow_info);
563 #define FLT_BUF_SIZE 1024
573 * XXX - the routine pointed to by "print_line" doesn't get handed lines,
574 * it gets handed bufferfuls. That's fine for "follow_write_raw()"
575 * and "follow_add_to_gtk_text()", but, as "follow_print_text()" calls
576 * the "print_line()" routine from "print.c", and as that routine might
577 * genuinely expect to be handed a line (if, for example, it's using
578 * some OS or desktop environment's printing API, and that API expects
579 * to be handed lines), "follow_print_text()" should probably accumulate
580 * lines in a buffer and hand them "print_line()". (If there's a
581 * complete line in a buffer - i.e., there's nothing of the line in
582 * the previous buffer or the next buffer - it can just hand that to
583 * "print_line()" after filtering out non-printables, as an
586 * This might or might not be the reason why C arrays display
587 * correctly but get extra blank lines very other line when printed.
590 follow_read_stream(follow_info_t *follow_info,
591 gboolean (*print_line) (char *, size_t, gboolean, void *),
596 guint8 client_addr[MAX_IPADDR_LEN];
597 guint16 client_port = 0;
599 guint32 current_pos, global_client_pos = 0, global_server_pos = 0;
603 guint32 server_packet_count = 0;
604 guint32 client_packet_count = 0;
605 char buffer[FLT_BUF_SIZE];
607 static const gchar hexchars[16] = "0123456789abcdef";
609 iplen = (follow_info->is_ipv6) ? 16 : 4;
611 data_out_file = eth_fopen(follow_info->data_out_filename, "rb");
612 if (data_out_file == NULL) {
613 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
614 "Could not open temporary file %s: %s", follow_info->data_out_filename,
616 return FRS_OPEN_ERROR;
619 while (fread(&sc, 1, sizeof(sc), data_out_file)) {
620 if (client_port == 0) {
621 memcpy(client_addr, sc.src_addr, iplen);
622 client_port = sc.src_port;
625 if (memcmp(client_addr, sc.src_addr, iplen) == 0 &&
626 client_port == sc.src_port) {
628 global_pos = &global_client_pos;
629 if (follow_info->show_stream == FROM_SERVER) {
635 global_pos = &global_server_pos;
636 if (follow_info->show_stream == FROM_CLIENT) {
641 while (sc.dlen > 0) {
642 bcount = (sc.dlen < FLT_BUF_SIZE) ? sc.dlen : FLT_BUF_SIZE;
643 nchars = fread(buffer, 1, bcount, data_out_file);
649 switch (follow_info->show_type) {
652 /* If our native arch is ASCII, call: */
653 EBCDIC_to_ASCII(buffer, nchars);
654 if (!(*print_line) (buffer, nchars, is_server, arg))
659 /* If our native arch is EBCDIC, call:
660 * ASCII_TO_EBCDIC(buffer, nchars);
662 if (!(*print_line) (buffer, nchars, is_server, arg))
667 /* Don't translate, no matter what the native arch
670 if (!(*print_line) (buffer, nchars, is_server, arg))
676 while (current_pos < nchars) {
679 gchar *cur = hexbuf, *ascii_start;
681 /* is_server indentation : put 78 spaces at the
682 * beginning of the string */
683 if (is_server && follow_info->show_stream == BOTH_HOSTS) {
684 memset(cur, ' ', 78);
687 cur += g_snprintf(cur, 20, "%08X ", *global_pos);
688 /* 49 is space consumed by hex chars */
689 ascii_start = cur + 49;
690 for (i = 0; i < 16 && current_pos + i < nchars; i++) {
692 hexchars[(buffer[current_pos + i] & 0xf0) >> 4];
694 hexchars[buffer[current_pos + i] & 0x0f];
699 /* Fill it up if column isn't complete */
700 while (cur < ascii_start)
703 /* Now dump bytes as text */
704 for (i = 0; i < 16 && current_pos + i < nchars; i++) {
706 (isprint((guchar)buffer[current_pos + i]) ?
707 buffer[current_pos + i] : '.' );
716 if (!(*print_line) (hexbuf, strlen(hexbuf), is_server, arg))
723 g_snprintf(initbuf, sizeof(initbuf), "char peer%d_%d[] = {\n",
725 is_server ? server_packet_count++ : client_packet_count++);
726 if (!(*print_line) (initbuf, strlen(initbuf), is_server, arg))
728 while (current_pos < nchars) {
733 for (i = 0; i < 8 && current_pos + i < nchars; i++) {
734 /* Prepend entries with "0x" */
738 hexchars[(buffer[current_pos + i] & 0xf0) >> 4];
740 hexchars[buffer[current_pos + i] & 0x0f];
742 /* Delimit array entries with a comma */
743 if (current_pos + i + 1 < nchars)
749 /* Terminate the array if we are at the end */
750 if (current_pos + i == nchars) {
757 hexbuf[cur++] = '\n';
759 if (!(*print_line) (hexbuf, strlen(hexbuf), is_server, arg))
767 if (ferror(data_out_file)) {
768 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
769 "Error reading temporary file %s: %s", follow_info->data_out_filename,
771 fclose(data_out_file);
772 data_out_file = NULL;
773 return FRS_READ_ERROR;
776 fclose(data_out_file);
777 data_out_file = NULL;
781 fclose(data_out_file);
782 data_out_file = NULL;
783 return FRS_PRINT_ERROR;
787 * XXX - for text printing, we probably want to wrap lines at 80 characters;
788 * (PostScript printing is doing this already), and perhaps put some kind of
789 * dingbat (to use the technical term) to indicate a wrapped line, along the
790 * lines of what's done when displaying this in a window, as per Warren Young's
794 follow_print_text(char *buffer, size_t nchars, gboolean is_server _U_, void *arg)
796 print_stream_t *stream = arg;
800 /* convert non printable characters */
801 for (i = 0; i < nchars; i++) {
802 if (buffer[i] == '\n' || buffer[i] == '\r')
804 if (! isprint((guchar)buffer[i])) {
809 /* convert unterminated char array to a zero terminated string */
810 str = g_malloc(nchars + 1);
811 memcpy(str, buffer, nchars);
813 print_line(stream, /*indent*/ 0, str);
820 follow_write_raw(char *buffer, size_t nchars, gboolean is_server _U_, void *arg)
825 nwritten = fwrite(buffer, 1, nchars, fh);
826 if (nwritten != nchars)
833 follow_filter_out_stream(GtkWidget * w _U_, gpointer data)
835 follow_info_t *follow_info = data;
837 /* Lock out user from messing with us. (ie. don't free our data!) */
838 gtk_widget_set_sensitive(follow_info->streamwindow, FALSE);
840 /* Set the display filter. */
841 gtk_entry_set_text(GTK_ENTRY(follow_info->filter_te), follow_info->filter_out_filter);
843 /* Run the display filter so it goes in effect. */
844 main_filter_packets(&cfile, follow_info->filter_out_filter, FALSE);
846 /* we force a subsequent close */
847 window_destroy(follow_info->streamwindow);
853 follow_print_stream(GtkWidget * w _U_, gpointer data)
855 print_stream_t *stream;
858 follow_info_t *follow_info = data;
860 gboolean win_printer = FALSE;
863 switch (prefs.pr_dest) {
867 /*XXX should use temp file stuff in util routines */
868 print_dest = g_strdup(tmpnam(NULL));
871 print_dest = prefs.pr_cmd;
876 print_dest = prefs.pr_file;
879 default: /* "Can't happen" */
880 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
881 "Couldn't figure out where to send the print "
882 "job. Check your preferences.");
886 switch (prefs.pr_format) {
889 stream = print_stream_text_new(to_file, print_dest);
893 stream = print_stream_ps_new(to_file, print_dest);
897 g_assert_not_reached();
900 if (stream == NULL) {
902 open_failure_alert_box(prefs.pr_file, errno, TRUE);
904 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
905 "Couldn't run print command %s.", prefs.pr_cmd);
910 if (!print_preamble(stream, cfile.filename))
913 switch (follow_read_stream(follow_info, follow_print_text, stream)) {
918 /* XXX - cancel printing? */
919 destroy_print_stream(stream);
921 case FRS_PRINT_ERROR:
925 if (!print_finale(stream))
928 if (!destroy_print_stream(stream)) {
930 write_failure_alert_box(prefs.pr_file, errno);
932 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
933 "Error closing print destination.");
938 print_mswin(print_dest);
940 /* trash temp file */
941 eth_remove(print_dest);
948 write_failure_alert_box(prefs.pr_file, errno);
950 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
951 "Error writing to print command: %s", strerror(errno));
953 /* XXX - cancel printing? */
954 destroy_print_stream(stream);
958 /* trash temp file */
959 eth_remove(print_dest);
964 /* static variable declarations to speed up the performance
965 * of follow_load_text and follow_add_to_gtk_text
967 static GdkColor server_fg, server_bg;
968 static GdkColor client_fg, client_bg;
969 #if GTK_MAJOR_VERSION >= 2
970 static GtkTextTag *server_tag, *client_tag;
974 follow_add_to_gtk_text(char *buffer, size_t nchars, gboolean is_server,
977 GtkWidget *text = arg;
978 #if GTK_MAJOR_VERSION >= 2
979 GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
983 #if GTK_MAJOR_VERSION >= 2 || GTK_MINOR_VERSION >= 3
984 /* While our isprint() hack is in place, we
985 * have to use convert some chars to '.' in order
986 * to be able to see the data we *should* see
987 * in the GtkText widget.
991 for (i = 0; i < nchars; i++) {
992 if (buffer[i] == '\n' || buffer[i] == '\r')
994 if (! isprint(buffer[i])) {
1000 #if GTK_MAJOR_VERSION < 2
1002 gtk_text_insert(GTK_TEXT(text), user_font_get_regular(), &server_fg,
1003 &server_bg, buffer, nchars);
1005 gtk_text_insert(GTK_TEXT(text), user_font_get_regular(), &client_fg,
1006 &client_bg, buffer, nchars);
1009 gtk_text_buffer_get_end_iter(buf, &iter);
1011 gtk_text_buffer_insert_with_tags(buf, &iter, buffer, nchars,
1014 gtk_text_buffer_insert_with_tags(buf, &iter, buffer, nchars,
1022 follow_load_text(follow_info_t *follow_info)
1024 #if GTK_MAJOR_VERSION < 2
1029 buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(follow_info->text));
1032 /* prepare colors one time for repeated use by follow_add_to_gtk_text */
1033 color_t_to_gdkcolor(&server_fg, &prefs.st_server_fg);
1034 color_t_to_gdkcolor(&server_bg, &prefs.st_server_bg);
1035 color_t_to_gdkcolor(&client_fg, &prefs.st_client_fg);
1036 color_t_to_gdkcolor(&client_bg, &prefs.st_client_bg);
1038 /* Delete any info already in text box */
1039 #if GTK_MAJOR_VERSION < 2
1040 bytes_already = gtk_text_get_length(GTK_TEXT(follow_info->text));
1041 if (bytes_already > 0) {
1042 gtk_text_set_point(GTK_TEXT(follow_info->text), 0);
1043 gtk_text_forward_delete(GTK_TEXT(follow_info->text), bytes_already);
1046 /* stop the updates while we fill the text box */
1047 gtk_text_freeze(GTK_TEXT(follow_info->text));
1049 /* prepare tags one time for repeated use by follow_add_to_gtk_text */
1050 server_tag = gtk_text_buffer_create_tag(buf, NULL, "foreground-gdk", &server_fg,
1051 "background-gdk", &server_bg, "font-desc",
1052 user_font_get_regular(), NULL);
1053 client_tag = gtk_text_buffer_create_tag(buf, NULL, "foreground-gdk", &client_fg,
1054 "background-gdk", &client_bg, "font-desc",
1055 user_font_get_regular(), NULL);
1057 gtk_text_buffer_set_text(buf, "", -1);
1059 follow_read_stream(follow_info, follow_add_to_gtk_text, follow_info->text);
1060 #if GTK_MAJOR_VERSION < 2
1061 gtk_text_thaw(GTK_TEXT(follow_info->text));
1067 * Keep a static pointer to the current "Save TCP Follow Stream As" window, if
1068 * any, so that if somebody tries to do "Save"
1069 * while there's already a "Save TCP Follow Stream" window up, we just pop
1070 * up the existing one, rather than creating a new one.
1073 follow_save_as_cmd_cb(GtkWidget *w _U_, gpointer data)
1076 follow_info_t *follow_info = data;
1078 if (follow_info->follow_save_as_w != NULL) {
1079 /* There's already a dialog box; reactivate it. */
1080 reactivate_window(follow_info->follow_save_as_w);
1084 new_win = file_selection_new("Ethereal: Save TCP Follow Stream As",
1085 FILE_SELECTION_SAVE);
1086 follow_info->follow_save_as_w = new_win;
1088 /* Tuck away the follow_info object into the window */
1089 OBJECT_SET_DATA(new_win, E_FOLLOW_INFO_KEY, follow_info);
1091 SIGNAL_CONNECT(new_win, "destroy", follow_save_as_destroy_cb, follow_info);
1093 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
1094 if (gtk_dialog_run(GTK_DIALOG(new_win)) == GTK_RESPONSE_ACCEPT)
1096 follow_save_as_ok_cb(new_win, new_win);
1098 window_destroy(new_win);
1101 /* Connect the ok_button to file_save_as_ok_cb function and pass along a
1102 pointer to the file selection box widget */
1103 SIGNAL_CONNECT(GTK_FILE_SELECTION(new_win)->ok_button,
1104 "clicked", follow_save_as_ok_cb, new_win);
1106 window_set_cancel_button(new_win,
1107 GTK_FILE_SELECTION(new_win)->cancel_button, window_cancel_button_cb);
1109 gtk_file_selection_set_filename(GTK_FILE_SELECTION(new_win), "");
1111 SIGNAL_CONNECT(new_win, "delete_event", window_delete_event_cb, NULL);
1113 gtk_widget_show_all(new_win);
1114 window_present(new_win);
1120 follow_save_as_ok_cb(GtkWidget * w _U_, gpointer fs)
1123 follow_info_t *follow_info;
1125 print_stream_t *stream = NULL;
1128 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
1129 to_name = g_strdup(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs)));
1131 to_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(fs)));
1134 /* Perhaps the user specified a directory instead of a file.
1135 Check whether they did. */
1136 if (test_for_directory(to_name) == EISDIR) {
1137 /* It's a directory - set the file selection box to display that
1138 directory, and leave the selection box displayed. */
1139 set_last_open_dir(to_name);
1141 file_selection_set_current_folder(fs, get_last_open_dir());
1145 follow_info = OBJECT_GET_DATA(fs, E_FOLLOW_INFO_KEY);
1146 if (follow_info->show_type == SHOW_RAW) {
1147 /* Write the data out as raw binary data */
1148 fh = eth_fopen(to_name, "wb");
1150 /* Write it out as text */
1151 fh = eth_fopen(to_name, "w");
1154 open_failure_alert_box(to_name, errno, TRUE);
1159 gtk_widget_hide(GTK_WIDGET(fs));
1160 window_destroy(GTK_WIDGET(fs));
1162 if (follow_info->show_type == SHOW_RAW) {
1163 switch (follow_read_stream(follow_info, follow_write_raw, fh)) {
1165 if (fclose(fh) == EOF)
1166 write_failure_alert_box(to_name, errno);
1169 case FRS_OPEN_ERROR:
1170 case FRS_READ_ERROR:
1174 case FRS_PRINT_ERROR:
1175 write_failure_alert_box(to_name, errno);
1180 stream = print_stream_text_stdio_new(fh);
1181 switch (follow_read_stream(follow_info, follow_print_text, stream)) {
1183 if (!destroy_print_stream(stream))
1184 write_failure_alert_box(to_name, errno);
1187 case FRS_OPEN_ERROR:
1188 case FRS_READ_ERROR:
1189 destroy_print_stream(stream);
1192 case FRS_PRINT_ERROR:
1193 write_failure_alert_box(to_name, errno);
1194 destroy_print_stream(stream);
1199 /* Save the directory name for future file dialogs. */
1200 dirname = get_dirname(to_name); /* Overwrites to_name */
1201 set_last_open_dir(dirname);
1206 follow_save_as_destroy_cb(GtkWidget * win _U_, gpointer data)
1208 follow_info_t *follow_info = data;
1210 /* Note that we no longer have a dialog box. */
1211 follow_info->follow_save_as_w = NULL;