add a help button to the "Follow TCP Stream" dialog (and use standard button row...
[obnox/wireshark/wip.git] / gtk / follow_dlg.c
1 /* follow_dlg.c
2  *
3  * $Id$
4  *
5  * Ethereal - Network traffic analyzer
6  * By Gerald Combs <gerald@ethereal.com>
7  * Copyright 2000 Gerald Combs
8  *
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.
13  *
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.
18  *
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.
22  */
23
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include <gtk/gtk.h>
29
30 #include <stdio.h>
31 #include <string.h>
32
33
34 #ifdef HAVE_UNISTD_H
35 #include <unistd.h>
36 #endif
37
38 #include <ctype.h>
39
40 #include "isprint.h"
41
42 #include "file_util.h"
43 #include "color.h"
44 #include "colors.h"
45 #include "file.h"
46 #include "follow_dlg.h"
47 #include <epan/follow.h>
48 #include "dlg_utils.h"
49 #include "keys.h"
50 #include "globals.h"
51 #include "main.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>
58 #include "tempfile.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"
66 #include "help_dlg.h"
67
68 /* This is backwards-compatibility code for old versions of GTK+ (2.2.1 and
69  * earlier).  It defines the new wrap behavior (unknown in earlier versions)
70  * as the old (slightly buggy) wrap behavior.
71  */
72 #ifndef GTK_WRAP_WORD_CHAR
73 #define GTK_WRAP_WORD_CHAR GTK_WRAP_WORD
74 #endif
75
76 /* Show Stream */
77 typedef enum {
78         FROM_CLIENT,
79         FROM_SERVER,
80         BOTH_HOSTS
81 } show_stream_t;
82
83 /* Show Type */
84 typedef enum {
85         SHOW_ASCII,
86         SHOW_EBCDIC,
87         SHOW_HEXDUMP,
88         SHOW_CARRAY,
89         SHOW_RAW
90 } show_type_t;
91
92 typedef struct {
93         show_stream_t   show_stream;
94         show_type_t     show_type;
95         char            data_out_filename[128 + 1];
96         GtkWidget       *text;
97         GtkWidget       *ascii_bt;
98         GtkWidget       *ebcdic_bt;
99         GtkWidget       *hexdump_bt;
100         GtkWidget       *carray_bt;
101         GtkWidget       *raw_bt;
102         GtkWidget       *follow_save_as_w;
103         gboolean        is_ipv6;
104         char            *filter_out_filter;
105         GtkWidget       *filter_te;
106         GtkWidget       *streamwindow;
107 } follow_info_t;
108
109 static void follow_destroy_cb(GtkWidget * win, gpointer data);
110 static void follow_charset_toggle_cb(GtkWidget * w, gpointer parent_w);
111 static void follow_load_text(follow_info_t *follow_info);
112 static void follow_filter_out_stream(GtkWidget * w, gpointer parent_w);
113 static void follow_print_stream(GtkWidget * w, gpointer parent_w);
114 static void follow_save_as_cmd_cb(GtkWidget * w, gpointer data);
115 static void follow_save_as_ok_cb(GtkWidget * w, gpointer fs);
116 static void follow_save_as_destroy_cb(GtkWidget * win, gpointer user_data);
117 static void follow_stream_om_both(GtkWidget * w, gpointer data);
118 static void follow_stream_om_client(GtkWidget * w, gpointer data);
119 static void follow_stream_om_server(GtkWidget * w, gpointer data);
120
121
122 /* With MSVC and a libethereal.dll, we need a special declaration. */
123 ETH_VAR_IMPORT FILE *data_out_file;
124
125 #define E_FOLLOW_INFO_KEY "follow_info_key"
126
127 /* List of "follow_info_t" structures for all "Follow TCP Stream" windows,
128    so we can redraw them all if the colors or font changes. */
129 static GList *follow_infos;
130
131 /* Add a "follow_info_t" structure to the list. */
132 static void
133 remember_follow_info(follow_info_t *follow_info)
134 {
135   follow_infos = g_list_append(follow_infos, follow_info);
136 }
137
138 /* Remove a "follow_info_t" structure from the list. */
139 static void
140 forget_follow_info(follow_info_t *follow_info)
141 {
142   follow_infos = g_list_remove(follow_infos, follow_info);
143 }
144
145 static void
146 follow_redraw(gpointer data, gpointer user_data _U_)
147 {
148         follow_load_text((follow_info_t *)data);
149 }
150
151 /* Redraw the text in all "Follow TCP Stream" windows. */
152 void
153 follow_redraw_all(void)
154 {
155         g_list_foreach(follow_infos, follow_redraw, NULL);
156 }
157
158 /* Follow the TCP stream, if any, to which the last packet that we called
159    a dissection routine on belongs (this might be the most recently
160    selected packet, or it might be the last packet in the file). */
161 void
162 follow_stream_cb(GtkWidget * w, gpointer data _U_)
163 {
164         GtkWidget       *streamwindow, *vbox, *txt_scrollw, *text, *filter_te;
165         GtkWidget       *hbox, *bbox, *button, *radio_bt;
166     GtkWidget   *stream_fr, *stream_vb;
167         GtkWidget       *stream_om, *stream_menu, *stream_mi;
168         GtkTooltips *tooltips;
169         int                 tmp_fd;
170         gchar           *follow_filter;
171         const gchar     *previous_filter;
172     int             filter_out_filter_len;
173         const char      *hostname0, *hostname1;
174         char            *port0, *port1;
175         char            string[128];
176         follow_tcp_stats_t stats;
177         follow_info_t   *follow_info;
178
179         /* we got tcp so we can follow */
180         if (cfile.edt->pi.ipproto != 6) {
181                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
182                               "Error following stream.  Please make\n"
183                               "sure you have a TCP packet selected.");
184                 return;
185         }
186
187         follow_info = g_new0(follow_info_t, 1);
188
189         /* Create a temporary file into which to dump the reassembled data
190            from the TCP stream, and set "data_out_file" to refer to it, so
191            that the TCP code will write to it.
192
193            XXX - it might be nicer to just have the TCP code directly
194            append stuff to the text widget for the TCP stream window,
195            if we can arrange that said window not pop up until we're
196            done. */
197         tmp_fd = create_tempfile(follow_info->data_out_filename,
198                         sizeof follow_info->data_out_filename, "follow");
199
200         if (tmp_fd == -1) {
201             simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
202                           "Could not create temporary file %s: %s",
203                           follow_info->data_out_filename, strerror(errno));
204             g_free(follow_info);
205             return;
206         }
207
208         data_out_file = fdopen(tmp_fd, "wb");
209         if (data_out_file == NULL) {
210             simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
211                           "Could not create temporary file %s: %s",
212                           follow_info->data_out_filename, strerror(errno));
213             eth_close(tmp_fd);
214             unlink(follow_info->data_out_filename);
215             g_free(follow_info);
216             return;
217         }
218
219         /* Create a new filter that matches all packets in the TCP stream,
220            and set the display filter entry accordingly */
221         reset_tcp_reassembly();
222         follow_filter = build_follow_filter(&cfile.edt->pi);
223
224         /* Set the display filter entry accordingly */
225         filter_te = OBJECT_GET_DATA(w, E_DFILTER_TE_KEY);
226
227         /* needed in follow_filter_out_stream(), is there a better way? */
228         follow_info->filter_te = filter_te;
229
230         /* save previous filter, const since we're not supposed to alter */
231         previous_filter =
232             (const gchar *)gtk_entry_get_text(GTK_ENTRY(filter_te));
233
234         /* allocate our new filter. API claims g_malloc terminates program on failure */
235         /* my calc for max alloc needed is really +10 but when did a few extra bytes hurt ? */
236         filter_out_filter_len = strlen(follow_filter) + strlen(previous_filter) + 16;
237         follow_info->filter_out_filter = (gchar *)g_malloc(filter_out_filter_len);
238
239         /* append the negation */
240         if(strlen(previous_filter)) {
241             g_snprintf(follow_info->filter_out_filter, filter_out_filter_len,
242             "%s and !(%s)", previous_filter, follow_filter);
243         } else {
244             g_snprintf(follow_info->filter_out_filter, filter_out_filter_len,
245             "!(%s)", follow_filter);
246         }
247
248
249         gtk_entry_set_text(GTK_ENTRY(filter_te), follow_filter);
250
251         /* Run the display filter so it goes in effect - even if it's the
252            same as the previous display filter. */
253         main_filter_packets(&cfile, follow_filter, TRUE);
254
255         /* Free the filter string, as we're done with it. */
256         g_free(follow_filter);
257
258         /* The data_out_file should now be full of the streams information */
259         fclose(data_out_file);
260
261         /* The data_out_filename file now has all the text that was in the session */
262         streamwindow = dlg_window_new("Follow TCP Stream");
263
264         /* needed in follow_filter_out_stream(), is there a better way? */
265         follow_info->streamwindow = streamwindow;
266
267         gtk_widget_set_name(streamwindow, "TCP stream window");
268         gtk_window_set_default_size(GTK_WINDOW(streamwindow), DEF_WIDTH, DEF_HEIGHT);
269         gtk_container_border_width(GTK_CONTAINER(streamwindow), 6);
270
271         /* setup the container */
272         tooltips = gtk_tooltips_new ();
273
274         vbox = gtk_vbox_new(FALSE, 6);
275         gtk_container_add(GTK_CONTAINER(streamwindow), vbox);
276
277     /* content frame */
278         if (incomplete_tcp_stream) {
279                 stream_fr = gtk_frame_new("Stream Content (incomplete)");
280         } else {
281                 stream_fr = gtk_frame_new("Stream Content");
282         }
283         gtk_container_add(GTK_CONTAINER(vbox), stream_fr);
284         gtk_widget_show(stream_fr);
285
286         stream_vb = gtk_vbox_new(FALSE, 6);
287         gtk_container_set_border_width( GTK_CONTAINER(stream_vb) , 6);
288         gtk_container_add(GTK_CONTAINER(stream_fr), stream_vb);
289
290         /* create a scrolled window for the text */
291         txt_scrollw = scrolled_window_new(NULL, NULL);
292 #if GTK_MAJOR_VERSION >= 2
293         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(txt_scrollw),
294                                             GTK_SHADOW_IN);
295 #endif
296         gtk_box_pack_start(GTK_BOX(stream_vb), txt_scrollw, TRUE, TRUE, 0);
297
298         /* create a text box */
299 #if GTK_MAJOR_VERSION < 2
300         text = gtk_text_new(NULL, NULL);
301         gtk_text_set_editable(GTK_TEXT(text), FALSE);
302 #else
303         text = gtk_text_view_new();
304         gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
305         gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
306 #endif
307         gtk_container_add(GTK_CONTAINER(txt_scrollw), text);
308         follow_info->text = text;
309
310
311         /* stream hbox */
312         hbox = gtk_hbox_new(FALSE, 1);
313         gtk_box_pack_start(GTK_BOX(stream_vb), hbox, FALSE, FALSE, 0);
314
315         /* Create Save As Button */
316         button = BUTTON_NEW_FROM_STOCK(GTK_STOCK_SAVE_AS);
317         SIGNAL_CONNECT(button, "clicked", follow_save_as_cmd_cb, follow_info);
318         gtk_tooltips_set_tip (tooltips, button, "Save the content as currently displayed ", NULL);
319         gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
320
321         /* Create Print Button */
322         button = BUTTON_NEW_FROM_STOCK(GTK_STOCK_PRINT);
323         SIGNAL_CONNECT(button, "clicked", follow_print_stream, follow_info);
324         gtk_tooltips_set_tip (tooltips, button, "Print the content as currently displayed", NULL);
325         gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
326
327         /* Stream to show */
328         follow_tcp_stats(&stats);
329
330         if (stats.is_ipv6) {
331           struct e_in6_addr ipaddr;
332           memcpy(&ipaddr, stats.ip_address[0], 16);
333           hostname0 = get_hostname6(&ipaddr);
334           memcpy(&ipaddr, stats.ip_address[0], 16);
335           hostname1 = get_hostname6(&ipaddr);
336         } else {
337           guint32 ipaddr;
338           memcpy(&ipaddr, stats.ip_address[0], 4);
339           hostname0 = get_hostname(ipaddr);
340           memcpy(&ipaddr, stats.ip_address[1], 4);
341           hostname1 = get_hostname(ipaddr);
342         }
343
344         port0 = get_tcp_port(stats.tcp_port[0]);
345         port1 = get_tcp_port(stats.tcp_port[1]);
346
347         follow_info->is_ipv6 = stats.is_ipv6;
348
349         stream_om = gtk_option_menu_new();
350         stream_menu = gtk_menu_new();
351
352         /* Both Stream Directions */
353         g_snprintf(string, sizeof(string),
354                  "Entire conversation (%u bytes)",
355                  stats.bytes_written[0] + stats.bytes_written[1]);
356         stream_mi = gtk_menu_item_new_with_label(string);
357         SIGNAL_CONNECT(stream_mi, "activate", follow_stream_om_both,
358                        follow_info);
359         gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
360         gtk_widget_show(stream_mi);
361         follow_info->show_stream = BOTH_HOSTS;
362
363         /* Host 0 --> Host 1 */
364         g_snprintf(string, sizeof(string), "%s:%s --> %s:%s (%u bytes)",
365                  hostname0, port0, hostname1, port1,
366                  stats.bytes_written[0]);
367         stream_mi = gtk_menu_item_new_with_label(string);
368         SIGNAL_CONNECT(stream_mi, "activate", follow_stream_om_client,
369                        follow_info);
370         gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
371         gtk_widget_show(stream_mi);
372
373         /* Host 1 --> Host 0 */
374         g_snprintf(string, sizeof(string), "%s:%s --> %s:%s (%u bytes)",
375                  hostname1, port1, hostname0, port0,
376                  stats.bytes_written[1]);
377         stream_mi = gtk_menu_item_new_with_label(string);
378         SIGNAL_CONNECT(stream_mi, "activate", follow_stream_om_server,
379                        follow_info);
380         gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
381         gtk_widget_show(stream_mi);
382
383         gtk_option_menu_set_menu(GTK_OPTION_MENU(stream_om), stream_menu);
384         /* Set history to 0th item, i.e., the first item. */
385         gtk_option_menu_set_history(GTK_OPTION_MENU(stream_om), 0);
386         gtk_tooltips_set_tip (tooltips, stream_om,
387             "Select the stream direction to display", NULL);
388         gtk_box_pack_start(GTK_BOX(hbox), stream_om, FALSE, FALSE, 0);
389
390         /* ASCII radio button */
391         radio_bt = gtk_radio_button_new_with_label(NULL, "ASCII");
392         gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"ASCII\" format", NULL);
393         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), TRUE);
394         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
395         SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
396                        follow_info);
397         follow_info->ascii_bt = radio_bt;
398         follow_info->show_type = SHOW_ASCII;
399
400         /* EBCDIC radio button */
401         radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
402                                             (GTK_RADIO_BUTTON(radio_bt)),
403                                             "EBCDIC");
404         gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"EBCDIC\" format", NULL);
405         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
406         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
407         SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
408                        follow_info);
409         follow_info->ebcdic_bt = radio_bt;
410
411         /* HEX DUMP radio button */
412         radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
413                                             (GTK_RADIO_BUTTON(radio_bt)),
414                                             "Hex Dump");
415         gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"Hexdump\" format", NULL);
416         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
417         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
418         SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
419                        follow_info);
420         follow_info->hexdump_bt = radio_bt;
421
422         /* C Array radio button */
423         radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
424                                             (GTK_RADIO_BUTTON(radio_bt)),
425                                             "C Arrays");
426         gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"C Array\" format", NULL);
427         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
428         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
429         SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
430                        follow_info);
431         follow_info->carray_bt = radio_bt;
432
433         /* Raw radio button */
434         radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
435                                             (GTK_RADIO_BUTTON(radio_bt)),
436                                             "Raw");
437         gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"Raw\" (binary) format. "
438         "As this contains non printable characters, the screen output will be in ASCII format", NULL);
439         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
440         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
441         SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
442                        follow_info);
443         follow_info->raw_bt = radio_bt;
444
445     /* Button row: (help), filter out, close button */
446     if(topic_available(HELP_FILESET_DIALOG)) {
447       bbox = dlg_button_row_new(ETHEREAL_STOCK_FILTER_OUT_STREAM, GTK_STOCK_CLOSE, GTK_STOCK_HELP, NULL);
448     } else {
449       bbox = dlg_button_row_new(ETHEREAL_STOCK_FILTER_OUT_STREAM, GTK_STOCK_CLOSE, NULL);
450     }
451     gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 5);
452
453
454     button = OBJECT_GET_DATA(bbox, ETHEREAL_STOCK_FILTER_OUT_STREAM);
455         gtk_tooltips_set_tip (tooltips, button,
456         "Build a display filter which cuts this stream from the capture", NULL);
457         SIGNAL_CONNECT(button, "clicked", follow_filter_out_stream, follow_info);
458
459     button = OBJECT_GET_DATA(bbox, GTK_STOCK_CLOSE);
460         window_set_cancel_button(streamwindow, button, window_cancel_button_cb);
461         gtk_tooltips_set_tip (tooltips, button,
462             "Close the dialog and keep the current display filter", NULL);
463     gtk_widget_grab_default(button);
464
465     if(topic_available(HELP_FILESET_DIALOG)) {
466       button = OBJECT_GET_DATA(bbox, GTK_STOCK_HELP);
467       SIGNAL_CONNECT(button, "clicked", topic_cb, HELP_FOLLOW_TCP_STREAM_DIALOG);
468     }
469
470         /* Tuck away the follow_info object into the window */
471         OBJECT_SET_DATA(streamwindow, E_FOLLOW_INFO_KEY, follow_info);
472
473         follow_load_text(follow_info);
474         remember_follow_info(follow_info);
475
476         data_out_file = NULL;
477
478         SIGNAL_CONNECT(streamwindow, "delete_event", window_delete_event_cb, NULL);
479         SIGNAL_CONNECT(streamwindow, "destroy", follow_destroy_cb, NULL);
480
481         /* Make sure this widget gets destroyed if we quit the main loop,
482            so that if we exit, we clean up any temporary files we have
483            for "Follow TCP Stream" windows. */
484         gtk_quit_add_destroy(gtk_main_level(), GTK_OBJECT(streamwindow));
485
486         gtk_widget_show_all(streamwindow);
487         window_present(streamwindow);
488 }
489
490 /* The destroy call back has the responsibility of
491  * unlinking the temporary file
492  * and freeing the filter_out_filter */
493 static void
494 follow_destroy_cb(GtkWidget *w, gpointer data _U_)
495 {
496         follow_info_t   *follow_info;
497         int i;
498
499         follow_info = OBJECT_GET_DATA(w, E_FOLLOW_INFO_KEY);
500         i = unlink(follow_info->data_out_filename);
501         if(i != 0) {
502                 g_warning("Follow: Couldn't remove temporary file: \"%s\", errno: %s (%u)", 
503                     follow_info->data_out_filename, strerror(errno), errno);        
504         }
505         g_free(follow_info->filter_out_filter);
506         forget_follow_info(follow_info);
507         g_free(follow_info);
508 }
509
510 /* XXX - can I emulate follow_charset_toggle_cb() instead of having
511  * 3 different functions here?
512  * That might not be a bad idea, as it might mean we only reload
513  * the window once, not twice - see follow_charset_toggle_cb()
514  * for an explanation. */
515 static void
516 follow_stream_om_both(GtkWidget *w _U_, gpointer data)
517 {
518         follow_info_t   *follow_info = data;
519         follow_info->show_stream = BOTH_HOSTS;
520         follow_load_text(follow_info);
521 }
522
523 static void
524 follow_stream_om_client(GtkWidget *w _U_, gpointer data)
525 {
526         follow_info_t   *follow_info = data;
527         follow_info->show_stream = FROM_CLIENT;
528         follow_load_text(follow_info);
529 }
530
531 static void
532 follow_stream_om_server(GtkWidget *w _U_, gpointer data)
533 {
534         follow_info_t   *follow_info = data;
535         follow_info->show_stream = FROM_SERVER;
536         follow_load_text(follow_info);
537 }
538
539
540 /* Handles the display style toggling */
541 static void
542 follow_charset_toggle_cb(GtkWidget * w _U_, gpointer data)
543 {
544         follow_info_t   *follow_info = data;
545
546         /*
547          * A radio button toggles when it goes on and when it goes
548          * off, so when you click a radio button two signals are
549          * delivered.  We only want to reprocess the display once,
550          * so we do it only when the button goes on.
551          */
552         if (GTK_TOGGLE_BUTTON(w)->active) {
553                 if (w == follow_info->ebcdic_bt)
554                         follow_info->show_type = SHOW_EBCDIC;
555                 else if (w == follow_info->hexdump_bt)
556                         follow_info->show_type = SHOW_HEXDUMP;
557                 else if (w == follow_info->carray_bt)
558                         follow_info->show_type = SHOW_CARRAY;
559                 else if (w == follow_info->ascii_bt)
560                         follow_info->show_type = SHOW_ASCII;
561                 else if (w == follow_info->raw_bt)
562                         follow_info->show_type = SHOW_RAW;
563                 follow_load_text(follow_info);
564         }
565 }
566
567 #define FLT_BUF_SIZE 1024
568
569 typedef enum {
570         FRS_OK,
571         FRS_OPEN_ERROR,
572         FRS_READ_ERROR,
573         FRS_PRINT_ERROR
574 } frs_return_t;
575
576 /*
577  * XXX - the routine pointed to by "print_line" doesn't get handed lines,
578  * it gets handed bufferfuls.  That's fine for "follow_write_raw()"
579  * and "follow_add_to_gtk_text()", but, as "follow_print_text()" calls
580  * the "print_line()" routine from "print.c", and as that routine might
581  * genuinely expect to be handed a line (if, for example, it's using
582  * some OS or desktop environment's printing API, and that API expects
583  * to be handed lines), "follow_print_text()" should probably accumulate
584  * lines in a buffer and hand them "print_line()".  (If there's a
585  * complete line in a buffer - i.e., there's nothing of the line in
586  * the previous buffer or the next buffer - it can just hand that to
587  * "print_line()" after filtering out non-printables, as an
588  * optimization.)
589  *
590  * This might or might not be the reason why C arrays display
591  * correctly but get extra blank lines very other line when printed.
592  */
593 static frs_return_t
594 follow_read_stream(follow_info_t *follow_info,
595                    gboolean (*print_line) (char *, size_t, gboolean, void *),
596                    void *arg)
597 {
598     tcp_stream_chunk    sc;
599     int                 bcount, iplen;
600     guint8              client_addr[MAX_IPADDR_LEN];
601     guint16             client_port = 0;
602     gboolean            is_server;
603     guint32             current_pos, global_client_pos = 0, global_server_pos = 0;
604     guint32             *global_pos;
605     gboolean            skip;
606     gchar               initbuf[256];
607     guint32             server_packet_count = 0;
608     guint32             client_packet_count = 0;
609     char                buffer[FLT_BUF_SIZE];
610     size_t              nchars;
611     static const gchar  hexchars[16] = "0123456789abcdef";
612
613     iplen = (follow_info->is_ipv6) ? 16 : 4;
614
615     data_out_file = eth_fopen(follow_info->data_out_filename, "rb");
616     if (data_out_file == NULL) {
617         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
618                       "Could not open temporary file %s: %s", follow_info->data_out_filename,
619                       strerror(errno));
620         return FRS_OPEN_ERROR;
621     }
622
623     while (fread(&sc, 1, sizeof(sc), data_out_file)) {
624         if (client_port == 0) {
625             memcpy(client_addr, sc.src_addr, iplen);
626             client_port = sc.src_port;
627         }
628         skip = FALSE;
629         if (memcmp(client_addr, sc.src_addr, iplen) == 0 &&
630             client_port == sc.src_port) {
631             is_server = FALSE;
632             global_pos = &global_client_pos;
633             if (follow_info->show_stream == FROM_SERVER) {
634                 skip = TRUE;
635             }
636         }
637         else {
638             is_server = TRUE;
639             global_pos = &global_server_pos;
640             if (follow_info->show_stream == FROM_CLIENT) {
641                 skip = TRUE;
642             }
643         }
644
645         while (sc.dlen > 0) {
646             bcount = (sc.dlen < FLT_BUF_SIZE) ? sc.dlen : FLT_BUF_SIZE;
647             nchars = fread(buffer, 1, bcount, data_out_file);
648             if (nchars == 0)
649                 break;
650             sc.dlen -= nchars;
651
652             if (!skip) {
653                 switch (follow_info->show_type) {
654
655                 case SHOW_EBCDIC:
656                     /* If our native arch is ASCII, call: */
657                     EBCDIC_to_ASCII(buffer, nchars);
658                     if (!(*print_line) (buffer, nchars, is_server, arg))
659                         goto print_error;
660                     break;
661
662                 case SHOW_ASCII:
663                     /* If our native arch is EBCDIC, call:
664                      * ASCII_TO_EBCDIC(buffer, nchars);
665                      */
666                     if (!(*print_line) (buffer, nchars, is_server, arg))
667                         goto print_error;
668                     break;
669
670                 case SHOW_RAW:
671                     /* Don't translate, no matter what the native arch
672                      * is.
673                      */
674                     if (!(*print_line) (buffer, nchars, is_server, arg))
675                         goto print_error;
676                     break;
677
678                 case SHOW_HEXDUMP:
679                     current_pos = 0;
680                     while (current_pos < nchars) {
681                         gchar hexbuf[256];
682                         int i;
683                         gchar *cur = hexbuf, *ascii_start;
684
685                         /* is_server indentation : put 78 spaces at the
686                          * beginning of the string */
687                         if (is_server && follow_info->show_stream == BOTH_HOSTS) {
688                             memset(cur, ' ', 78);
689                             cur += 78;
690                         }
691                         cur += g_snprintf(cur, 20, "%08X  ", *global_pos);
692                         /* 49 is space consumed by hex chars */
693                         ascii_start = cur + 49;
694                         for (i = 0; i < 16 && current_pos + i < nchars; i++) {
695                             *cur++ =
696                                 hexchars[(buffer[current_pos + i] & 0xf0) >> 4];
697                             *cur++ =
698                                 hexchars[buffer[current_pos + i] & 0x0f];
699                             *cur++ = ' ';
700                             if (i == 7)
701                                 *cur++ = ' ';
702                         }
703                         /* Fill it up if column isn't complete */
704                         while (cur < ascii_start)  
705                             *cur++ = ' ';
706
707                         /* Now dump bytes as text */
708                         for (i = 0; i < 16 && current_pos + i < nchars; i++) {
709                             *cur++ =
710                                 (isprint((guchar)buffer[current_pos + i]) ?
711                                 buffer[current_pos + i] : '.' );
712                             if (i == 7) {
713                                 *cur++ = ' ';
714                             }
715                         }
716                         current_pos += i;
717                         (*global_pos) += i;
718                         *cur++ = '\n';
719                         *cur = 0;
720                         if (!(*print_line) (hexbuf, strlen(hexbuf), is_server, arg))
721                             goto print_error;
722                     }
723                     break;
724
725                 case SHOW_CARRAY:
726                     current_pos = 0;
727                     g_snprintf(initbuf, sizeof(initbuf), "char peer%d_%d[] = {\n", 
728                             is_server ? 1 : 0, 
729                             is_server ? server_packet_count++ : client_packet_count++);
730                     if (!(*print_line) (initbuf, strlen(initbuf), is_server, arg))
731                         goto print_error;
732                     while (current_pos < nchars) {
733                         gchar hexbuf[256];
734                         int i, cur;
735
736                         cur = 0;
737                         for (i = 0; i < 8 && current_pos + i < nchars; i++) {
738                           /* Prepend entries with "0x" */
739                           hexbuf[cur++] = '0';
740                           hexbuf[cur++] = 'x';
741                             hexbuf[cur++] =
742                                 hexchars[(buffer[current_pos + i] & 0xf0) >> 4];
743                             hexbuf[cur++] =
744                                 hexchars[buffer[current_pos + i] & 0x0f];
745
746                             /* Delimit array entries with a comma */
747                             if (current_pos + i + 1 < nchars)
748                               hexbuf[cur++] = ',';
749
750                             hexbuf[cur++] = ' ';
751                         }
752
753                         /* Terminate the array if we are at the end */
754                         if (current_pos + i == nchars) {
755                             hexbuf[cur++] = '}';
756                             hexbuf[cur++] = ';';
757                         }
758
759                         current_pos += i;
760                         (*global_pos) += i;
761                         hexbuf[cur++] = '\n';
762                         hexbuf[cur] = 0;
763                         if (!(*print_line) (hexbuf, strlen(hexbuf), is_server, arg))
764                             goto print_error;
765                     }
766                     break;
767                 }
768             }
769         }
770     }
771     if (ferror(data_out_file)) {
772         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
773                       "Error reading temporary file %s: %s", follow_info->data_out_filename,
774                       strerror(errno));
775         fclose(data_out_file);
776         data_out_file = NULL;
777         return FRS_READ_ERROR;
778     }
779
780         fclose(data_out_file);
781         data_out_file = NULL;
782     return FRS_OK;
783
784 print_error:
785     fclose(data_out_file);
786     data_out_file = NULL;
787     return FRS_PRINT_ERROR;
788 }
789
790 /*
791  * XXX - for text printing, we probably want to wrap lines at 80 characters;
792  * (PostScript printing is doing this already), and perhaps put some kind of 
793  * dingbat (to use the technical term) to indicate a wrapped line, along the 
794  * lines of what's done when displaying this in a window, as per Warren Young's 
795  * suggestion.
796  */
797 static gboolean
798 follow_print_text(char *buffer, size_t nchars, gboolean is_server _U_, void *arg)
799 {
800     print_stream_t *stream = arg;
801     size_t i;
802     char *str;
803
804     /* convert non printable characters */
805     for (i = 0; i < nchars; i++) {
806         if (buffer[i] == '\n' || buffer[i] == '\r')
807             continue;
808         if (! isprint((guchar)buffer[i])) {
809             buffer[i] = '.';
810         }
811     }
812
813     /* convert unterminated char array to a zero terminated string */
814     str = g_malloc(nchars + 1);
815     memcpy(str, buffer, nchars);
816     str[nchars] = 0;
817     print_line(stream, /*indent*/ 0, str);
818     g_free(str);
819
820     return TRUE;
821 }
822
823 static gboolean
824 follow_write_raw(char *buffer, size_t nchars, gboolean is_server _U_, void *arg)
825 {
826     FILE *fh = arg;
827     size_t nwritten;
828
829     nwritten = fwrite(buffer, 1, nchars, fh);
830     if (nwritten != nchars)
831         return FALSE;
832
833     return TRUE;
834 }
835
836 static void
837 follow_filter_out_stream(GtkWidget * w _U_, gpointer data)
838 {
839     follow_info_t       *follow_info = data;
840
841     /* Lock out user from messing with us. (ie. don't free our data!) */
842     gtk_widget_set_sensitive(follow_info->streamwindow, FALSE);
843
844     /* Set the display filter. */
845     gtk_entry_set_text(GTK_ENTRY(follow_info->filter_te), follow_info->filter_out_filter);
846
847     /* Run the display filter so it goes in effect. */
848     main_filter_packets(&cfile, follow_info->filter_out_filter, FALSE);
849
850     /* we force a subsequent close */
851     window_destroy(follow_info->streamwindow);
852
853     return;
854 }
855
856 static void
857 follow_print_stream(GtkWidget * w _U_, gpointer data)
858 {
859     print_stream_t      *stream;
860     gboolean            to_file;
861     char                *print_dest;
862     follow_info_t       *follow_info = data;
863 #ifdef _WIN32
864     gboolean win_printer = FALSE;
865 #endif
866
867     switch (prefs.pr_dest) {
868     case PR_DEST_CMD:
869 #ifdef _WIN32
870         win_printer = TRUE;
871         /*XXX should use temp file stuff in util routines */
872         print_dest = g_strdup(tmpnam(NULL));
873         to_file = TRUE;
874 #else
875         print_dest = prefs.pr_cmd;
876         to_file = FALSE;
877 #endif
878         break;
879     case PR_DEST_FILE:
880         print_dest = prefs.pr_file;
881         to_file = TRUE;
882         break;
883     default:                    /* "Can't happen" */
884         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
885             "Couldn't figure out where to send the print "
886             "job. Check your preferences.");
887         return;
888     }
889
890     switch (prefs.pr_format) {
891
892     case PR_FMT_TEXT:
893       stream = print_stream_text_new(to_file, print_dest);
894       break;
895
896     case PR_FMT_PS:
897       stream = print_stream_ps_new(to_file, print_dest);
898       break;
899
900     default:
901       g_assert_not_reached();
902       stream = NULL;
903     }
904     if (stream == NULL) {
905         if (to_file) {
906             open_failure_alert_box(prefs.pr_file, errno, TRUE);
907         } else {
908             simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
909                       "Couldn't run print command %s.", prefs.pr_cmd);
910         }
911         return;
912     }
913
914     if (!print_preamble(stream, cfile.filename))
915         goto print_error;
916
917     switch (follow_read_stream(follow_info, follow_print_text, stream)) {
918     case FRS_OK:
919         break;
920     case FRS_OPEN_ERROR:
921     case FRS_READ_ERROR:
922             /* XXX - cancel printing? */
923             destroy_print_stream(stream);
924             return;
925     case FRS_PRINT_ERROR:
926             goto print_error;
927     }
928
929     if (!print_finale(stream))
930             goto print_error;
931
932     if (!destroy_print_stream(stream)) {
933         if (to_file) {
934           write_failure_alert_box(prefs.pr_file, errno);
935         } else {
936             simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
937                           "Error closing print destination.");
938         }
939     }
940 #ifdef _WIN32
941     if (win_printer) {
942         print_mswin(print_dest);
943
944         /* trash temp file */
945         eth_remove(print_dest);
946     }
947 #endif
948     return;
949
950 print_error:
951     if (to_file) {
952         write_failure_alert_box(prefs.pr_file, errno);
953     } else {
954         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
955           "Error writing to print command: %s", strerror(errno));
956     }
957     /* XXX - cancel printing? */
958     destroy_print_stream(stream);
959
960 #ifdef _WIN32
961     if (win_printer) {
962         /* trash temp file */
963         eth_remove(print_dest);
964     }
965 #endif
966 }
967
968 /* static variable declarations to speed up the performance
969  * of follow_load_text and follow_add_to_gtk_text
970  */
971 static GdkColor server_fg, server_bg;
972 static GdkColor client_fg, client_bg;
973 #if GTK_MAJOR_VERSION >= 2
974 static GtkTextTag *server_tag, *client_tag;
975 #endif
976
977 static gboolean
978 follow_add_to_gtk_text(char *buffer, size_t nchars, gboolean is_server,
979                        void *arg)
980 {
981     GtkWidget *text = arg;
982 #if GTK_MAJOR_VERSION >= 2
983     GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
984     GtkTextIter    iter;
985 #endif
986
987 #if GTK_MAJOR_VERSION >= 2 || GTK_MINOR_VERSION >= 3
988     /* While our isprint() hack is in place, we
989      * have to use convert some chars to '.' in order
990      * to be able to see the data we *should* see
991      * in the GtkText widget.
992      */
993     size_t i;
994
995     for (i = 0; i < nchars; i++) {
996         if (buffer[i] == '\n' || buffer[i] == '\r')
997             continue;
998         if (! isprint(buffer[i])) {
999             buffer[i] = '.';
1000         }
1001     }
1002 #endif
1003
1004 #if GTK_MAJOR_VERSION < 2
1005     if (is_server) {
1006         gtk_text_insert(GTK_TEXT(text), user_font_get_regular(), &server_fg, 
1007                         &server_bg, buffer, nchars);
1008     } else {
1009         gtk_text_insert(GTK_TEXT(text), user_font_get_regular(), &client_fg, 
1010                         &client_bg, buffer, nchars);
1011     }
1012 #else
1013     gtk_text_buffer_get_end_iter(buf, &iter);
1014     if (is_server) {
1015         gtk_text_buffer_insert_with_tags(buf, &iter, buffer, nchars, 
1016                                         server_tag, NULL);
1017     } else {
1018         gtk_text_buffer_insert_with_tags(buf, &iter, buffer, nchars, 
1019                                         client_tag, NULL);
1020     }
1021 #endif
1022     return TRUE;
1023 }
1024
1025 static void
1026 follow_load_text(follow_info_t *follow_info)
1027 {
1028 #if GTK_MAJOR_VERSION < 2
1029     int bytes_already;
1030 #else
1031     GtkTextBuffer *buf;
1032
1033     buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(follow_info->text));
1034 #endif
1035
1036     /* prepare colors one time for repeated use by follow_add_to_gtk_text */
1037     color_t_to_gdkcolor(&server_fg, &prefs.st_server_fg);
1038     color_t_to_gdkcolor(&server_bg, &prefs.st_server_bg);
1039     color_t_to_gdkcolor(&client_fg, &prefs.st_client_fg);
1040     color_t_to_gdkcolor(&client_bg, &prefs.st_client_bg);
1041
1042     /* Delete any info already in text box */
1043 #if GTK_MAJOR_VERSION < 2
1044     bytes_already = gtk_text_get_length(GTK_TEXT(follow_info->text));
1045     if (bytes_already > 0) {
1046         gtk_text_set_point(GTK_TEXT(follow_info->text), 0);
1047         gtk_text_forward_delete(GTK_TEXT(follow_info->text), bytes_already);
1048     }
1049
1050     /* stop the updates while we fill the text box */
1051     gtk_text_freeze(GTK_TEXT(follow_info->text));
1052 #else
1053     /* prepare tags one time for repeated use by follow_add_to_gtk_text */
1054     server_tag = gtk_text_buffer_create_tag(buf, NULL, "foreground-gdk", &server_fg,
1055                                      "background-gdk", &server_bg, "font-desc",
1056                                      user_font_get_regular(), NULL);
1057     client_tag = gtk_text_buffer_create_tag(buf, NULL, "foreground-gdk", &client_fg,
1058                                      "background-gdk", &client_bg, "font-desc",
1059                                      user_font_get_regular(), NULL);
1060
1061     gtk_text_buffer_set_text(buf, "", -1);
1062 #endif
1063     follow_read_stream(follow_info, follow_add_to_gtk_text, follow_info->text);
1064 #if GTK_MAJOR_VERSION < 2
1065     gtk_text_thaw(GTK_TEXT(follow_info->text));
1066 #endif
1067 }
1068
1069
1070 /*
1071  * Keep a static pointer to the current "Save TCP Follow Stream As" window, if
1072  * any, so that if somebody tries to do "Save"
1073  * while there's already a "Save TCP Follow Stream" window up, we just pop
1074  * up the existing one, rather than creating a new one.
1075  */
1076 static void
1077 follow_save_as_cmd_cb(GtkWidget *w _U_, gpointer data)
1078 {
1079     GtkWidget           *new_win;
1080     follow_info_t       *follow_info = data;
1081
1082     if (follow_info->follow_save_as_w != NULL) {
1083         /* There's already a dialog box; reactivate it. */
1084         reactivate_window(follow_info->follow_save_as_w);
1085         return;
1086     }
1087
1088     new_win = file_selection_new("Ethereal: Save TCP Follow Stream As",
1089                                  FILE_SELECTION_SAVE);
1090     follow_info->follow_save_as_w = new_win;
1091
1092     /* Tuck away the follow_info object into the window */
1093     OBJECT_SET_DATA(new_win, E_FOLLOW_INFO_KEY, follow_info);
1094
1095     SIGNAL_CONNECT(new_win, "destroy", follow_save_as_destroy_cb, follow_info);
1096
1097 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
1098     if (gtk_dialog_run(GTK_DIALOG(new_win)) == GTK_RESPONSE_ACCEPT)
1099     {
1100         follow_save_as_ok_cb(new_win, new_win);
1101     } else {
1102         window_destroy(new_win);
1103     }
1104 #else
1105     /* Connect the ok_button to file_save_as_ok_cb function and pass along a
1106        pointer to the file selection box widget */
1107     SIGNAL_CONNECT(GTK_FILE_SELECTION(new_win)->ok_button, 
1108         "clicked", follow_save_as_ok_cb, new_win);
1109
1110     window_set_cancel_button(new_win, 
1111         GTK_FILE_SELECTION(new_win)->cancel_button, window_cancel_button_cb);
1112
1113     gtk_file_selection_set_filename(GTK_FILE_SELECTION(new_win), "");
1114
1115     SIGNAL_CONNECT(new_win, "delete_event", window_delete_event_cb, NULL);
1116
1117     gtk_widget_show_all(new_win);
1118     window_present(new_win);
1119 #endif
1120 }
1121
1122
1123 static void
1124 follow_save_as_ok_cb(GtkWidget * w _U_, gpointer fs)
1125 {
1126     gchar               *to_name;
1127     follow_info_t       *follow_info;
1128     FILE                *fh;
1129     print_stream_t      *stream = NULL;
1130     gchar               *dirname;
1131
1132 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
1133     to_name = g_strdup(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs)));
1134 #else
1135     to_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(fs)));
1136 #endif
1137
1138     /* Perhaps the user specified a directory instead of a file.
1139        Check whether they did. */
1140     if (test_for_directory(to_name) == EISDIR) {
1141         /* It's a directory - set the file selection box to display that
1142            directory, and leave the selection box displayed. */
1143         set_last_open_dir(to_name);
1144         g_free(to_name);
1145         file_selection_set_current_folder(fs, get_last_open_dir());
1146         return;
1147     }
1148
1149     follow_info = OBJECT_GET_DATA(fs, E_FOLLOW_INFO_KEY);
1150     if (follow_info->show_type == SHOW_RAW) {
1151         /* Write the data out as raw binary data */
1152         fh = eth_fopen(to_name, "wb");
1153     } else {
1154         /* Write it out as text */
1155         fh = eth_fopen(to_name, "w");
1156     }
1157     if (fh == NULL) {
1158         open_failure_alert_box(to_name, errno, TRUE);
1159         g_free(to_name);
1160         return;
1161     }
1162
1163     gtk_widget_hide(GTK_WIDGET(fs));
1164     window_destroy(GTK_WIDGET(fs));
1165
1166     if (follow_info->show_type == SHOW_RAW) {
1167         switch (follow_read_stream(follow_info, follow_write_raw, fh)) {
1168         case FRS_OK:
1169             if (fclose(fh) == EOF)
1170                 write_failure_alert_box(to_name, errno);
1171             break;
1172
1173         case FRS_OPEN_ERROR:
1174         case FRS_READ_ERROR:
1175             fclose(fh);
1176             break;
1177
1178         case FRS_PRINT_ERROR:
1179             write_failure_alert_box(to_name, errno);
1180             fclose(fh);
1181             break;
1182         }
1183     } else {
1184         stream = print_stream_text_stdio_new(fh);
1185         switch (follow_read_stream(follow_info, follow_print_text, stream)) {
1186         case FRS_OK:
1187             if (!destroy_print_stream(stream))
1188                 write_failure_alert_box(to_name, errno);
1189             break;
1190
1191         case FRS_OPEN_ERROR:
1192         case FRS_READ_ERROR:
1193             destroy_print_stream(stream);
1194             break;
1195
1196         case FRS_PRINT_ERROR:
1197             write_failure_alert_box(to_name, errno);
1198             destroy_print_stream(stream);
1199             break;
1200         }
1201     }
1202
1203     /* Save the directory name for future file dialogs. */
1204     dirname = get_dirname(to_name);  /* Overwrites to_name */
1205     set_last_open_dir(dirname);
1206     g_free(to_name);
1207 }
1208
1209 static void
1210 follow_save_as_destroy_cb(GtkWidget * win _U_, gpointer data)
1211 {
1212         follow_info_t   *follow_info = data;
1213
1214         /* Note that we no longer have a dialog box. */
1215         follow_info->follow_save_as_w = NULL;
1216 }