Workaround for bug 852. Force a line break so that the text view doesn't blow up...
[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 != IP_PROTO_TCP) {
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     gboolean line_break = FALSE;
989     /* While our isprint() hack is in place, we
990      * have to use convert some chars to '.' in order
991      * to be able to see the data we *should* see
992      * in the GtkText widget.
993      */
994     size_t i;
995
996     for (i = 0; i < nchars; i++) {
997         if (buffer[i] == '\n' || buffer[i] == '\r')
998         {
999             line_break = TRUE;
1000             continue;
1001         }
1002         if (! isprint(buffer[i])) {
1003             buffer[i] = '.';
1004         }
1005     }
1006
1007     /* XXX - workaround for bug 852
1008      * Force a line break so that the text view 
1009      * doesn't blow up on excessive long lines.
1010      */
1011     if (line_break == FALSE)
1012         buffer[--i] = '\n';
1013 #endif
1014
1015 #if GTK_MAJOR_VERSION < 2
1016     if (is_server) {
1017         gtk_text_insert(GTK_TEXT(text), user_font_get_regular(), &server_fg, 
1018                         &server_bg, buffer, nchars);
1019     } else {
1020         gtk_text_insert(GTK_TEXT(text), user_font_get_regular(), &client_fg, 
1021                         &client_bg, buffer, nchars);
1022     }
1023 #else
1024     gtk_text_buffer_get_end_iter(buf, &iter);
1025     if (is_server) {
1026         gtk_text_buffer_insert_with_tags(buf, &iter, buffer, nchars, 
1027                                         server_tag, NULL);
1028     } else {
1029         gtk_text_buffer_insert_with_tags(buf, &iter, buffer, nchars, 
1030                                         client_tag, NULL);
1031     }
1032 #endif
1033     return TRUE;
1034 }
1035
1036 static void
1037 follow_load_text(follow_info_t *follow_info)
1038 {
1039 #if GTK_MAJOR_VERSION < 2
1040     int bytes_already;
1041 #else
1042     GtkTextBuffer *buf;
1043
1044     buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(follow_info->text));
1045 #endif
1046
1047     /* prepare colors one time for repeated use by follow_add_to_gtk_text */
1048     color_t_to_gdkcolor(&server_fg, &prefs.st_server_fg);
1049     color_t_to_gdkcolor(&server_bg, &prefs.st_server_bg);
1050     color_t_to_gdkcolor(&client_fg, &prefs.st_client_fg);
1051     color_t_to_gdkcolor(&client_bg, &prefs.st_client_bg);
1052
1053     /* Delete any info already in text box */
1054 #if GTK_MAJOR_VERSION < 2
1055     bytes_already = gtk_text_get_length(GTK_TEXT(follow_info->text));
1056     if (bytes_already > 0) {
1057         gtk_text_set_point(GTK_TEXT(follow_info->text), 0);
1058         gtk_text_forward_delete(GTK_TEXT(follow_info->text), bytes_already);
1059     }
1060
1061     /* stop the updates while we fill the text box */
1062     gtk_text_freeze(GTK_TEXT(follow_info->text));
1063 #else
1064     /* prepare tags one time for repeated use by follow_add_to_gtk_text */
1065     server_tag = gtk_text_buffer_create_tag(buf, NULL, "foreground-gdk", &server_fg,
1066                                      "background-gdk", &server_bg, "font-desc",
1067                                      user_font_get_regular(), NULL);
1068     client_tag = gtk_text_buffer_create_tag(buf, NULL, "foreground-gdk", &client_fg,
1069                                      "background-gdk", &client_bg, "font-desc",
1070                                      user_font_get_regular(), NULL);
1071
1072     gtk_text_buffer_set_text(buf, "", -1);
1073 #endif
1074     follow_read_stream(follow_info, follow_add_to_gtk_text, follow_info->text);
1075 #if GTK_MAJOR_VERSION < 2
1076     gtk_text_thaw(GTK_TEXT(follow_info->text));
1077 #endif
1078 }
1079
1080
1081 /*
1082  * Keep a static pointer to the current "Save TCP Follow Stream As" window, if
1083  * any, so that if somebody tries to do "Save"
1084  * while there's already a "Save TCP Follow Stream" window up, we just pop
1085  * up the existing one, rather than creating a new one.
1086  */
1087 static void
1088 follow_save_as_cmd_cb(GtkWidget *w _U_, gpointer data)
1089 {
1090     GtkWidget           *new_win;
1091     follow_info_t       *follow_info = data;
1092
1093     if (follow_info->follow_save_as_w != NULL) {
1094         /* There's already a dialog box; reactivate it. */
1095         reactivate_window(follow_info->follow_save_as_w);
1096         return;
1097     }
1098
1099     new_win = file_selection_new("Ethereal: Save TCP Follow Stream As",
1100                                  FILE_SELECTION_SAVE);
1101     follow_info->follow_save_as_w = new_win;
1102
1103     /* Tuck away the follow_info object into the window */
1104     OBJECT_SET_DATA(new_win, E_FOLLOW_INFO_KEY, follow_info);
1105
1106     SIGNAL_CONNECT(new_win, "destroy", follow_save_as_destroy_cb, follow_info);
1107
1108 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
1109     if (gtk_dialog_run(GTK_DIALOG(new_win)) == GTK_RESPONSE_ACCEPT)
1110     {
1111         follow_save_as_ok_cb(new_win, new_win);
1112     } else {
1113         window_destroy(new_win);
1114     }
1115 #else
1116     /* Connect the ok_button to file_save_as_ok_cb function and pass along a
1117        pointer to the file selection box widget */
1118     SIGNAL_CONNECT(GTK_FILE_SELECTION(new_win)->ok_button, 
1119         "clicked", follow_save_as_ok_cb, new_win);
1120
1121     window_set_cancel_button(new_win, 
1122         GTK_FILE_SELECTION(new_win)->cancel_button, window_cancel_button_cb);
1123
1124     gtk_file_selection_set_filename(GTK_FILE_SELECTION(new_win), "");
1125
1126     SIGNAL_CONNECT(new_win, "delete_event", window_delete_event_cb, NULL);
1127
1128     gtk_widget_show_all(new_win);
1129     window_present(new_win);
1130 #endif
1131 }
1132
1133
1134 static void
1135 follow_save_as_ok_cb(GtkWidget * w _U_, gpointer fs)
1136 {
1137     gchar               *to_name;
1138     follow_info_t       *follow_info;
1139     FILE                *fh;
1140     print_stream_t      *stream = NULL;
1141     gchar               *dirname;
1142
1143 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
1144     to_name = g_strdup(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs)));
1145 #else
1146     to_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(fs)));
1147 #endif
1148
1149     /* Perhaps the user specified a directory instead of a file.
1150        Check whether they did. */
1151     if (test_for_directory(to_name) == EISDIR) {
1152         /* It's a directory - set the file selection box to display that
1153            directory, and leave the selection box displayed. */
1154         set_last_open_dir(to_name);
1155         g_free(to_name);
1156         file_selection_set_current_folder(fs, get_last_open_dir());
1157         return;
1158     }
1159
1160     follow_info = OBJECT_GET_DATA(fs, E_FOLLOW_INFO_KEY);
1161     if (follow_info->show_type == SHOW_RAW) {
1162         /* Write the data out as raw binary data */
1163         fh = eth_fopen(to_name, "wb");
1164     } else {
1165         /* Write it out as text */
1166         fh = eth_fopen(to_name, "w");
1167     }
1168     if (fh == NULL) {
1169         open_failure_alert_box(to_name, errno, TRUE);
1170         g_free(to_name);
1171         return;
1172     }
1173
1174     gtk_widget_hide(GTK_WIDGET(fs));
1175     window_destroy(GTK_WIDGET(fs));
1176
1177     if (follow_info->show_type == SHOW_RAW) {
1178         switch (follow_read_stream(follow_info, follow_write_raw, fh)) {
1179         case FRS_OK:
1180             if (fclose(fh) == EOF)
1181                 write_failure_alert_box(to_name, errno);
1182             break;
1183
1184         case FRS_OPEN_ERROR:
1185         case FRS_READ_ERROR:
1186             fclose(fh);
1187             break;
1188
1189         case FRS_PRINT_ERROR:
1190             write_failure_alert_box(to_name, errno);
1191             fclose(fh);
1192             break;
1193         }
1194     } else {
1195         stream = print_stream_text_stdio_new(fh);
1196         switch (follow_read_stream(follow_info, follow_print_text, stream)) {
1197         case FRS_OK:
1198             if (!destroy_print_stream(stream))
1199                 write_failure_alert_box(to_name, errno);
1200             break;
1201
1202         case FRS_OPEN_ERROR:
1203         case FRS_READ_ERROR:
1204             destroy_print_stream(stream);
1205             break;
1206
1207         case FRS_PRINT_ERROR:
1208             write_failure_alert_box(to_name, errno);
1209             destroy_print_stream(stream);
1210             break;
1211         }
1212     }
1213
1214     /* Save the directory name for future file dialogs. */
1215     dirname = get_dirname(to_name);  /* Overwrites to_name */
1216     set_last_open_dir(dirname);
1217     g_free(to_name);
1218 }
1219
1220 static void
1221 follow_save_as_destroy_cb(GtkWidget * win _U_, gpointer data)
1222 {
1223         follow_info_t   *follow_info = data;
1224
1225         /* Note that we no longer have a dialog box. */
1226         follow_info->follow_save_as_w = NULL;
1227 }