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