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