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