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