From Graeme Hewson:
[obnox/wireshark/wip.git] / gtk / follow_dlg.c
1 /* follow_dlg.c
2  *
3  * $Id: follow_dlg.c,v 1.28 2002/09/09 20:38:58 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 #ifdef NEED_SNPRINTF_H
42 # include "snprintf.h"
43 #endif
44
45 #include <ctype.h>
46
47 #include "color.h"
48 #include "color_utils.h"
49 #include "file.h"
50 #include "follow_dlg.h"
51 #include "follow.h"
52 #include "dlg_utils.h"
53 #include "keys.h"
54 #include "globals.h"
55 #include "gtkglobals.h"
56 #include "main.h"
57 #include "simple_dialog.h"
58 #include "packet-ipv6.h"
59 #include "prefs.h"
60 #include <epan/resolv.h>
61 #include "util.h"
62 #include "ui_util.h"
63 #include <epan/epan_dissect.h>
64 #include <epan/filesystem.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_type_t;
79
80 typedef struct {
81         show_stream_t   show_stream;
82         show_type_t     show_type;
83         char            data_out_filename[128 + 1];
84         GtkWidget       *text;
85         GtkWidget       *ascii_bt;
86         GtkWidget       *ebcdic_bt;
87         GtkWidget       *hexdump_bt;
88         GtkWidget       *follow_save_as_w;
89         gboolean        is_ipv6;
90 } follow_info_t;
91
92 static void follow_destroy_cb(GtkWidget * win, gpointer data);
93 static void follow_charset_toggle_cb(GtkWidget * w, gpointer parent_w);
94 static void follow_load_text(follow_info_t *follow_info);
95 static void follow_print_stream(GtkWidget * w, gpointer parent_w);
96 static void follow_save_as_cmd_cb(GtkWidget * w, gpointer data);
97 static void follow_save_as_ok_cb(GtkWidget * w, GtkFileSelection * fs);
98 static void follow_save_as_destroy_cb(GtkWidget * win, gpointer user_data);
99 static void follow_stream_om_both(GtkWidget * w, gpointer data);
100 static void follow_stream_om_client(GtkWidget * w, gpointer data);
101 static void follow_stream_om_server(GtkWidget * w, gpointer data);
102
103
104 extern FILE *data_out_file;
105
106
107 #define E_FOLLOW_INFO_KEY "follow_info_key"
108
109 /* List of "follow_info_t" structures for all "Follow TCP Stream" windows,
110    so we can redraw them all if the colors or font changes. */
111 static GList *follow_infos;
112
113 /* Add a "follow_info_t" structure to the list. */
114 static void
115 remember_follow_info(follow_info_t *follow_info)
116 {
117   follow_infos = g_list_append(follow_infos, follow_info);
118 }
119
120 /* Remove a "follow_info_t" structure from the list. */
121 static void
122 forget_follow_info(follow_info_t *follow_info)
123 {
124   follow_infos = g_list_remove(follow_infos, follow_info);
125 }
126
127 static void
128 follow_redraw(gpointer data, gpointer user_data _U_)
129 {
130         follow_load_text((follow_info_t *)data);
131 }
132
133 /* Redraw the text in all "Follow TCP Stream" windows. */
134 void
135 follow_redraw_all(void)
136 {
137         g_list_foreach(follow_infos, follow_redraw, NULL);
138 }
139
140 /* Follow the TCP stream, if any, to which the last packet that we called
141    a dissection routine on belongs (this might be the most recently
142    selected packet, or it might be the last packet in the file). */
143 void
144 follow_stream_cb(GtkWidget * w, gpointer data _U_)
145 {
146         GtkWidget       *streamwindow, *vbox, *txt_scrollw, *text, *filter_te;
147         GtkWidget       *hbox, *button, *radio_bt;
148         GtkWidget       *stream_om, *stream_menu, *stream_mi;
149         int             tmp_fd;
150         gchar           *follow_filter;
151         const char      *hostname0, *hostname1;
152         char            *port0, *port1;
153         char            string[128];
154         follow_tcp_stats_t stats;
155         follow_info_t   *follow_info;
156
157         /* we got tcp so we can follow */
158         if (cfile.edt->pi.ipproto != 6) {
159                 simple_dialog(ESD_TYPE_CRIT, NULL,
160                               "Error following stream.  Please make\n"
161                               "sure you have a TCP packet selected.");
162                 return;
163         }
164
165         follow_info = g_new0(follow_info_t, 1);
166
167         /* Create a temporary file into which to dump the reassembled data
168            from the TCP stream, and set "data_out_file" to refer to it, so
169            that the TCP code will write to it.
170
171            XXX - it might be nicer to just have the TCP code directly
172            append stuff to the text widget for the TCP stream window,
173            if we can arrange that said window not pop up until we're
174            done. */
175         tmp_fd = create_tempfile(follow_info->data_out_filename,
176                         sizeof follow_info->data_out_filename, "follow");
177
178         if (tmp_fd == -1) {
179             simple_dialog(ESD_TYPE_WARN, NULL,
180                           "Could not create temporary file %s: %s",
181                           follow_info->data_out_filename, strerror(errno));
182             g_free(follow_info);
183             return;
184         }
185
186         data_out_file = fdopen(tmp_fd, "wb");
187         if (data_out_file == NULL) {
188             simple_dialog(ESD_TYPE_WARN, NULL,
189                           "Could not create temporary file %s: %s",
190                           follow_info->data_out_filename, strerror(errno));
191             close(tmp_fd);
192             unlink(follow_info->data_out_filename);
193             g_free(follow_info);
194             return;
195         }
196
197         /* Create a new filter that matches all packets in the TCP stream,
198            and set the display filter entry accordingly */
199         reset_tcp_reassembly();
200         follow_filter = build_follow_filter(&cfile.edt->pi);
201
202         /* Set the display filter entry accordingly */
203         filter_te = gtk_object_get_data(GTK_OBJECT(w), E_DFILTER_TE_KEY);
204         gtk_entry_set_text(GTK_ENTRY(filter_te), follow_filter);
205
206         /* Run the display filter so it goes in effect. */
207         filter_packets(&cfile, follow_filter);
208
209         /* Free the filter string, as we're done with it. */
210         g_free(follow_filter);
211
212         /* The data_out_file should now be full of the streams information */
213         fclose(data_out_file);
214
215         /* The data_out_filename file now has all the text that was in the session */
216         streamwindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
217         gtk_widget_set_name(streamwindow, "TCP stream window");
218
219         gtk_signal_connect(GTK_OBJECT(streamwindow), "destroy",
220                            GTK_SIGNAL_FUNC(follow_destroy_cb), NULL);
221
222         gtk_signal_connect (GTK_OBJECT (streamwindow), "realize",
223                            GTK_SIGNAL_FUNC (window_icon_realize_cb), NULL);
224         if (incomplete_tcp_stream) {
225             gtk_window_set_title(GTK_WINDOW(streamwindow),
226                                  "Contents of TCP stream (incomplete)");
227         } else {
228             gtk_window_set_title(GTK_WINDOW(streamwindow),
229                                  "Contents of TCP stream");
230         }
231         gtk_widget_set_usize(GTK_WIDGET(streamwindow), DEF_WIDTH,
232                              DEF_HEIGHT);
233         gtk_container_border_width(GTK_CONTAINER(streamwindow), 2);
234
235         /* setup the container */
236         vbox = gtk_vbox_new(FALSE, 0);
237         gtk_container_add(GTK_CONTAINER(streamwindow), vbox);
238
239         /* create a scrolled window for the text */
240         txt_scrollw = scrolled_window_new(NULL, NULL);
241         gtk_box_pack_start(GTK_BOX(vbox), txt_scrollw, TRUE, TRUE, 0);
242         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(txt_scrollw),
243                                        GTK_POLICY_NEVER,
244                                        GTK_POLICY_ALWAYS);
245
246         /* create a text box */
247         text = gtk_text_new(NULL, NULL);
248         gtk_text_set_editable(GTK_TEXT(text), FALSE);
249         gtk_container_add(GTK_CONTAINER(txt_scrollw), text);
250         follow_info->text = text;
251
252         /* Create hbox */
253         hbox = gtk_hbox_new(FALSE, 1);
254         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
255
256
257         /* Stream to show */
258         follow_tcp_stats(&stats);
259
260         if (stats.is_ipv6) {
261           struct e_in6_addr ipaddr;
262           memcpy(&ipaddr, stats.ip_address[0], 16);
263           hostname0 = get_hostname6(&ipaddr);
264           memcpy(&ipaddr, stats.ip_address[0], 16);
265           hostname1 = get_hostname6(&ipaddr);
266         } else {
267           guint32 ipaddr;
268           memcpy(&ipaddr, stats.ip_address[0], 4);
269           hostname0 = get_hostname(ipaddr);
270           memcpy(&ipaddr, stats.ip_address[1], 4);
271           hostname1 = get_hostname(ipaddr);
272         }
273
274         port0 = get_tcp_port(stats.tcp_port[0]);
275         port1 = get_tcp_port(stats.tcp_port[1]);
276
277         follow_info->is_ipv6 = stats.is_ipv6;
278
279         stream_om = gtk_option_menu_new();
280         stream_menu = gtk_menu_new();
281
282         /* Both Hosts */
283         snprintf(string, sizeof(string),
284                  "Entire conversation (%u bytes)",
285                  stats.bytes_written[0] + stats.bytes_written[1]);
286         stream_mi = gtk_menu_item_new_with_label(string);
287         gtk_signal_connect(GTK_OBJECT(stream_mi), "activate",
288                            GTK_SIGNAL_FUNC(follow_stream_om_both), follow_info);
289         gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
290         gtk_widget_show(stream_mi);
291         follow_info->show_stream = BOTH_HOSTS;
292
293         /* Host 0 --> Host 1 */
294         snprintf(string, sizeof(string), "%s:%s --> %s:%s (%u bytes)",
295                  hostname0, port0, hostname1, port1,
296                  stats.bytes_written[0]);
297         stream_mi = gtk_menu_item_new_with_label(string);
298         gtk_signal_connect(GTK_OBJECT(stream_mi), "activate",
299                            GTK_SIGNAL_FUNC(follow_stream_om_client), follow_info);
300         gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
301         gtk_widget_show(stream_mi);
302
303         /* Host 1 --> Host 0 */
304         snprintf(string, sizeof(string), "%s:%s --> %s:%s (%u bytes)",
305                  hostname1, port1, hostname0, port0,
306                  stats.bytes_written[1]);
307         stream_mi = gtk_menu_item_new_with_label(string);
308         gtk_signal_connect(GTK_OBJECT(stream_mi), "activate",
309                            GTK_SIGNAL_FUNC(follow_stream_om_server), follow_info);
310         gtk_menu_append(GTK_MENU(stream_menu), stream_mi);
311         gtk_widget_show(stream_mi);
312
313         gtk_option_menu_set_menu(GTK_OPTION_MENU(stream_om), stream_menu);
314         /* Set history to 0th item, i.e., the first item. */
315         gtk_option_menu_set_history(GTK_OPTION_MENU(stream_om), 0);
316         gtk_box_pack_start(GTK_BOX(hbox), stream_om, FALSE, FALSE, 0);
317
318         /* ASCII radio button */
319         radio_bt = gtk_radio_button_new_with_label(NULL, "ASCII");
320         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), TRUE);
321         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
322         gtk_signal_connect(GTK_OBJECT(radio_bt), "toggled",
323                            GTK_SIGNAL_FUNC(follow_charset_toggle_cb),
324                            follow_info);
325         follow_info->ascii_bt = radio_bt;
326         follow_info->show_type = SHOW_ASCII;
327
328         /* EBCDIC radio button */
329         radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
330                                             (GTK_RADIO_BUTTON(radio_bt)),
331                                             "EBCDIC");
332         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
333         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
334         gtk_signal_connect(GTK_OBJECT(radio_bt), "toggled",
335                            GTK_SIGNAL_FUNC(follow_charset_toggle_cb),
336                            follow_info);
337         follow_info->ebcdic_bt = radio_bt;
338
339         /* HEX DUMP radio button */
340         radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
341                                             (GTK_RADIO_BUTTON(radio_bt)),
342                                             "Hex Dump");
343         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
344         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
345         gtk_signal_connect(GTK_OBJECT(radio_bt), "toggled",
346                            GTK_SIGNAL_FUNC(follow_charset_toggle_cb),
347                            follow_info);
348         follow_info->hexdump_bt = radio_bt;
349
350         /* Create Close Button */
351         button = gtk_button_new_with_label("Close");
352         gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
353                                   GTK_SIGNAL_FUNC(gtk_widget_destroy),
354                                   GTK_OBJECT(streamwindow));
355         gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
356
357         /* Catch the "key_press_event" signal in the window, so that we can catch
358         the ESC key being pressed and act as if the "Cancel" button had
359         been selected. */
360         dlg_set_cancel(streamwindow, button);
361
362         /* Create Save As Button */
363         button = gtk_button_new_with_label("Save As");
364         gtk_signal_connect(GTK_OBJECT(button), "clicked",
365                            GTK_SIGNAL_FUNC(follow_save_as_cmd_cb),
366                            follow_info);
367         gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
368
369         /* Create Print Button */
370         button = gtk_button_new_with_label("Print");
371         gtk_signal_connect(GTK_OBJECT(button), "clicked",
372                            GTK_SIGNAL_FUNC(follow_print_stream), follow_info);
373         gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
374
375
376         /* Tuck away the follow_info object into the window */
377         gtk_object_set_data(GTK_OBJECT(streamwindow), E_FOLLOW_INFO_KEY,
378                             follow_info);
379
380         follow_load_text(follow_info);
381         remember_follow_info(follow_info);
382
383         data_out_file = NULL;
384
385         /* Make sure this widget gets destroyed if we quit the main loop,
386            so that if we exit, we clean up any temporary files we have
387            for "Follow TCP Stream" windows. */
388         gtk_quit_add_destroy(gtk_main_level(), GTK_OBJECT(streamwindow));
389         gtk_widget_show_all(streamwindow);
390 }
391
392 /* The destroy call back has the responsibility of
393  * unlinking the temporary file */
394 static void
395 follow_destroy_cb(GtkWidget *w, gpointer data _U_)
396 {
397         follow_info_t   *follow_info;
398
399         follow_info = gtk_object_get_data(GTK_OBJECT(w), E_FOLLOW_INFO_KEY);
400         unlink(follow_info->data_out_filename);
401         gtk_widget_destroy(w);
402         forget_follow_info(follow_info);
403         g_free(follow_info);
404 }
405
406 /* XXX - can I emulate follow_charset_toggle_cb() instead of having
407  * 3 different functions here? */
408 static void
409 follow_stream_om_both(GtkWidget *w _U_, gpointer data)
410 {
411         follow_info_t   *follow_info = data;
412         follow_info->show_stream = BOTH_HOSTS;
413         follow_load_text(follow_info);
414 }
415
416 static void
417 follow_stream_om_client(GtkWidget *w _U_, gpointer data)
418 {
419         follow_info_t   *follow_info = data;
420         follow_info->show_stream = FROM_CLIENT;
421         follow_load_text(follow_info);
422 }
423
424 static void
425 follow_stream_om_server(GtkWidget *w _U_, gpointer data)
426 {
427         follow_info_t   *follow_info = data;
428         follow_info->show_stream = FROM_SERVER;
429         follow_load_text(follow_info);
430 }
431
432
433 /* Handles the ASCII/EBCDIC toggling */
434 static void
435 follow_charset_toggle_cb(GtkWidget * w _U_, gpointer data)
436 {
437         follow_info_t   *follow_info = data;
438
439         if (GTK_TOGGLE_BUTTON(follow_info->ebcdic_bt)->active)
440                 follow_info->show_type = SHOW_EBCDIC;
441         else if (GTK_TOGGLE_BUTTON(follow_info->hexdump_bt)->active)
442                 follow_info->show_type = SHOW_HEXDUMP;
443         else if (GTK_TOGGLE_BUTTON(follow_info->ascii_bt)->active)
444                 follow_info->show_type = SHOW_ASCII;
445         else
446                 g_assert_not_reached();
447
448         follow_load_text(follow_info);
449 }
450
451 #define FLT_BUF_SIZE 1024
452 static void
453 follow_read_stream(follow_info_t *follow_info,
454                    void (*print_line) (char *, int, gboolean, void *),
455                    void *arg)
456 {
457     tcp_stream_chunk    sc;
458     int                 bcount, iplen;
459     guint8              client_addr[MAX_IPADDR_LEN];
460     guint16             client_port = 0;
461     gboolean            is_server;
462     guint16             current_pos, global_client_pos = 0, global_server_pos = 0;
463     guint16             *global_pos;
464     gboolean            skip;
465
466     iplen = (follow_info->is_ipv6) ? 16 : 4;
467
468     data_out_file = fopen(follow_info->data_out_filename, "rb");
469     if (data_out_file) {
470         char buffer[FLT_BUF_SIZE];
471         int nchars;
472         while (fread(&sc, 1, sizeof(sc), data_out_file)) {
473             if (client_port == 0) {
474                 memcpy(client_addr, sc.src_addr, iplen);
475                 client_port = sc.src_port;
476             }
477             skip = FALSE;
478             if (memcmp(client_addr, sc.src_addr, iplen) == 0 &&
479                 client_port == sc.src_port) {
480                 is_server = FALSE;
481                 global_pos = &global_client_pos;
482                 if (follow_info->show_stream == FROM_SERVER) {
483                         skip = TRUE;
484                 }
485             }
486             else {
487                 is_server = TRUE;
488                 global_pos = &global_server_pos;
489                 if (follow_info->show_stream == FROM_CLIENT) {
490                         skip = TRUE;
491                 }
492             }
493
494             while (sc.dlen > 0) {
495                 bcount = (sc.dlen < FLT_BUF_SIZE) ? sc.dlen : FLT_BUF_SIZE;
496                 nchars = fread(buffer, 1, bcount, data_out_file);
497                 if (nchars == 0)
498                     break;
499                 sc.dlen -= bcount;
500                 if (!skip) {
501                     switch (follow_info->show_type) {
502                         case SHOW_EBCDIC:
503                             /* If our native arch is ASCII, call: */
504                             EBCDIC_to_ASCII(buffer, nchars);
505                             (*print_line) (buffer, nchars, is_server, arg);
506                             break;
507                         case SHOW_ASCII:
508                             /* If our native arch is EBCDIC, call:
509                              * ASCII_TO_EBCDIC(buffer, nchars);
510                              */
511                             (*print_line) (buffer, nchars, is_server, arg);
512                             break;
513                         case SHOW_HEXDUMP:
514                             current_pos = 0;
515                             while (current_pos < nchars) {
516                                 gchar hexbuf[256];
517                                 gchar hexchars[] = "0123456789abcdef";
518                                 int i, cur;
519                                 /* is_server indentation : put 63 spaces at the begenning
520                                  * of the string */
521                                 sprintf(hexbuf, (is_server &&
522                                         follow_info->show_stream == BOTH_HOSTS) ?
523                                         "                                 "
524                                         "                                             %08X  " :
525                                         "%08X  ", *global_pos);
526                                 cur = strlen(hexbuf);
527                                 for (i = 0; i < 16 && current_pos + i < nchars;
528                                      i++) {
529                                     hexbuf[cur++] =
530                                         hexchars[(buffer[current_pos + i] & 0xf0)
531                                                  >> 4];
532                                     hexbuf[cur++] =
533                                         hexchars[buffer[current_pos + i] & 0x0f];
534                                     if (i == 7) {
535                                         hexbuf[cur++] = ' ';
536                                         hexbuf[cur++] = ' ';
537                                     } else if (i != 15)
538                                         hexbuf[cur++] = ' ';
539                                 }
540                                 /* Fill it up if column isn't complete */
541                                 if (i < 16) {
542                                     int j;
543
544                                     for (j = i; j < 16; j++) {
545                                         if (j == 7)
546                                             hexbuf[cur++] = ' ';
547                                         hexbuf[cur++] = ' ';
548                                         hexbuf[cur++] = ' ';
549                                         hexbuf[cur++] = ' ';
550                                     }
551                                 } else
552                                     hexbuf[cur++] = ' ';
553
554                                 /* Now dump bytes as text */
555                                 for (i = 0; i < 16 && current_pos + i < nchars;
556                                      i++) {
557                                     hexbuf[cur++] =
558                                         (isprint((guchar)buffer[current_pos + i]) ?
559                                         buffer[current_pos + i] : '.' );
560                                     if (i == 7) {
561                                         hexbuf[cur++] = ' ';
562                                     }
563                                 }
564                                 current_pos += i;
565                                 (*global_pos) += i;
566                                 hexbuf[cur++] = '\n';
567                                 hexbuf[cur] = 0;
568                                 (*print_line) (hexbuf, strlen(hexbuf), is_server, arg);
569                             }
570                             break;
571                     }
572                 }
573             }
574         }
575         if (ferror(data_out_file)) {
576             simple_dialog(ESD_TYPE_WARN, NULL,
577                           "Error reading temporary file %s: %s", follow_info->data_out_filename,
578                           strerror(errno));
579         }
580         fclose(data_out_file);
581         data_out_file = NULL;
582     } else {
583         simple_dialog(ESD_TYPE_WARN, NULL,
584                       "Could not open temporary file %s: %s", follow_info->data_out_filename,
585                       strerror(errno));
586     }
587 }
588
589 /*
590  * XXX - for text printing, we probably want to wrap lines at 80 characters;
591  * for PostScript printing, we probably want to wrap them at the appropriate
592  * width, and perhaps put some kind of dingbat (to use the technical term)
593  * to indicate a wrapped line, along the lines of what's done when displaying
594  * this in a window, as per Warren Young's suggestion.
595  *
596  * For now, we support only text printing.
597  */
598 static void
599 follow_print_text(char *buffer, int nchars, gboolean is_server _U_, void *arg)
600 {
601     FILE *fh = arg;
602
603     fwrite(buffer, nchars, 1, fh);
604 }
605
606 static void
607 follow_print_stream(GtkWidget * w _U_, gpointer data)
608 {
609     FILE                *fh;
610     gboolean            to_file;
611     char                *print_dest;
612     follow_info_t       *follow_info = data;
613
614     switch (prefs.pr_dest) {
615     case PR_DEST_CMD:
616         print_dest = prefs.pr_cmd;
617         to_file = FALSE;
618         break;
619
620     case PR_DEST_FILE:
621         print_dest = prefs.pr_file;
622         to_file = TRUE;
623         break;
624     default:                    /* "Can't happen" */
625         simple_dialog(ESD_TYPE_CRIT, NULL,
626                       "Couldn't figure out where to send the print "
627                       "job. Check your preferences.");
628         return;
629     }
630
631     fh = open_print_dest(to_file, print_dest);
632     if (fh == NULL) {
633         switch (to_file) {
634         case FALSE:
635             simple_dialog(ESD_TYPE_WARN, NULL,
636                           "Couldn't run print command %s.", prefs.pr_cmd);
637             break;
638
639         case TRUE:
640             simple_dialog(ESD_TYPE_WARN, NULL,
641                           file_write_error_message(errno), prefs.pr_file);
642             break;
643         }
644         return;
645     }
646
647     print_preamble(fh, PR_FMT_TEXT);
648     follow_read_stream(follow_info, follow_print_text, fh);
649     print_finale(fh, PR_FMT_TEXT);
650     close_print_dest(to_file, fh);
651 }
652
653 static void
654 follow_add_to_gtk_text(char *buffer, int nchars, gboolean is_server,
655                        void *arg)
656 {
657     GtkWidget *text = arg;
658     GdkColor   fg, bg;
659
660 #ifdef _WIN32
661     /* While our isprint() hack is in place, we
662      * have to use convert some chars to '.' in order
663      * to be able to see the data we *should* see
664      * in the GtkText widget.
665      */
666     int i;
667
668     for (i = 0; i < nchars; i++) {
669             if (buffer[i] == 0x0a || buffer[i] == 0x0d) {
670                     continue;
671             }
672             else if (! isprint(buffer[i])) {
673                     buffer[i] = '.';
674             }
675     }
676 #endif
677
678     if (is_server) {
679         color_t_to_gdkcolor(&fg, &prefs.st_server_fg);
680         color_t_to_gdkcolor(&bg, &prefs.st_server_bg);
681     } else {
682         color_t_to_gdkcolor(&fg, &prefs.st_client_fg);
683         color_t_to_gdkcolor(&bg, &prefs.st_client_bg);
684     }
685     gtk_text_insert(GTK_TEXT(text), m_r_font, &fg, &bg, buffer, nchars);
686 }
687
688 static void
689 follow_load_text(follow_info_t *follow_info)
690 {
691     int bytes_already;
692
693     /* Delete any info already in text box */
694     bytes_already = gtk_text_get_length(GTK_TEXT(follow_info->text));
695     if (bytes_already > 0) {
696         gtk_text_set_point(GTK_TEXT(follow_info->text), 0);
697         gtk_text_forward_delete(GTK_TEXT(follow_info->text), bytes_already);
698     }
699
700     /* stop the updates while we fill the text box */
701     gtk_text_freeze(GTK_TEXT(follow_info->text));
702     follow_read_stream(follow_info, follow_add_to_gtk_text, follow_info->text);
703     gtk_text_thaw(GTK_TEXT(follow_info->text));
704 }
705
706
707 /*
708  * Keep a static pointer to the current "Save TCP Follow Stream As" window, if
709  * any, so that if somebody tries to do "Save"
710  * while there's already a "Save TCP Follow Stream" window up, we just pop
711  * up the existing one, rather than creating a new one.
712  */
713 static void
714 follow_save_as_cmd_cb(GtkWidget *w _U_, gpointer data)
715 {
716     GtkWidget           *ok_bt, *new_win;
717     follow_info_t       *follow_info = data;
718
719     if (follow_info->follow_save_as_w != NULL) {
720         /* There's already a dialog box; reactivate it. */
721         reactivate_window(follow_info->follow_save_as_w);
722         return;
723     }
724
725     new_win = gtk_file_selection_new("Ethereal: Save TCP Follow Stream As");
726     follow_info->follow_save_as_w = new_win;
727     gtk_signal_connect(GTK_OBJECT(new_win), "destroy",
728                     GTK_SIGNAL_FUNC(follow_save_as_destroy_cb), follow_info);
729
730     /* Tuck away the follow_info object into the window */
731     gtk_object_set_data(GTK_OBJECT(new_win), E_FOLLOW_INFO_KEY,
732                             follow_info);
733
734     /* If we've opened a file, start out by showing the files in the directory
735        in which that file resided. */
736     if (last_open_dir)
737         gtk_file_selection_set_filename(GTK_FILE_SELECTION(new_win),
738                                     last_open_dir);
739
740     /* Connect the ok_button to file_save_as_ok_cb function and pass along a
741        pointer to the file selection box widget */
742     ok_bt = GTK_FILE_SELECTION(new_win)->ok_button;
743     gtk_signal_connect(GTK_OBJECT(ok_bt), "clicked",
744                        (GtkSignalFunc) follow_save_as_ok_cb,
745                        new_win);
746
747     /* Connect the cancel_button to destroy the widget */
748     gtk_signal_connect_object(GTK_OBJECT(GTK_FILE_SELECTION
749                                          (new_win)->cancel_button),
750                               "clicked",
751                               (GtkSignalFunc) gtk_widget_destroy,
752                               GTK_OBJECT(new_win));
753
754     /* Catch the "key_press_event" signal in the window, so that we can catch
755        the ESC key being pressed and act as if the "Cancel" button had
756        been selected. */
757     dlg_set_cancel(new_win,
758                    GTK_FILE_SELECTION(new_win)->cancel_button);
759
760     gtk_file_selection_set_filename(GTK_FILE_SELECTION(new_win), "");
761     gtk_widget_show_all(new_win);
762 }
763
764
765 static void
766 follow_save_as_ok_cb(GtkWidget * w _U_, GtkFileSelection * fs)
767 {
768         gchar           *to_name;
769         follow_info_t   *follow_info;
770         FILE            *fh;
771         gchar           *dirname;
772
773         to_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(fs)));
774
775         /* Perhaps the user specified a directory instead of a file.
776            Check whether they did. */
777         if (test_for_directory(to_name) == EISDIR) {
778                 /* It's a directory - set the file selection box to display that
779                    directory, and leave the selection box displayed. */
780                 set_last_open_dir(to_name);
781                 g_free(to_name);
782                 gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs),
783                         last_open_dir);
784                 return;
785         }
786
787         fh = fopen(to_name, "wb");
788         if (fh == NULL) {
789                 simple_dialog(ESD_TYPE_WARN, NULL,
790                         file_write_error_message(errno), to_name);
791                 g_free(to_name);
792                 return;
793         }
794
795         gtk_widget_hide(GTK_WIDGET(fs));
796         follow_info = gtk_object_get_data(GTK_OBJECT(fs), E_FOLLOW_INFO_KEY);
797         gtk_widget_destroy(GTK_WIDGET(fs));
798
799         follow_read_stream(follow_info, follow_print_text, fh);
800         fclose(fh);
801
802         /* Save the directory name for future file dialogs. */
803         dirname = get_dirname(to_name);  /* Overwrites to_name */
804         set_last_open_dir(dirname);
805         g_free(to_name);
806 }
807
808 static void
809 follow_save_as_destroy_cb(GtkWidget * win _U_, gpointer data)
810 {
811         follow_info_t   *follow_info = data;
812
813         /* Note that we no longer have a dialog box. */
814         follow_info->follow_save_as_w = NULL;
815 }