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