replace *a lot* of file related calls by their GLib counterparts. This is necessary...
[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 "util.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 /* 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_RAW
81 } show_type_t;
82
83 typedef struct {
84         show_stream_t   show_stream;
85         show_type_t     show_type;
86         char            data_out_filename[128 + 1];
87         GtkWidget       *text;
88         GtkWidget       *ascii_bt;
89         GtkWidget       *ebcdic_bt;
90         GtkWidget       *hexdump_bt;
91         GtkWidget       *carray_bt;
92         GtkWidget       *raw_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             eth_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 Stream Directions */
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_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"ASCII\" format", NULL);
383         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), TRUE);
384         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
385         SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
386                        follow_info);
387         follow_info->ascii_bt = radio_bt;
388         follow_info->show_type = SHOW_ASCII;
389
390         /* EBCDIC radio button */
391         radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
392                                             (GTK_RADIO_BUTTON(radio_bt)),
393                                             "EBCDIC");
394         gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"EBCDIC\" format", NULL);
395         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
396         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
397         SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
398                        follow_info);
399         follow_info->ebcdic_bt = radio_bt;
400
401         /* HEX DUMP radio button */
402         radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
403                                             (GTK_RADIO_BUTTON(radio_bt)),
404                                             "Hex Dump");
405         gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"Hexdump\" format", NULL);
406         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
407         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
408         SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
409                        follow_info);
410         follow_info->hexdump_bt = radio_bt;
411
412         /* C Array radio button */
413         radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
414                                             (GTK_RADIO_BUTTON(radio_bt)),
415                                             "C Arrays");
416         gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"C Array\" format", NULL);
417         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
418         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
419         SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
420                        follow_info);
421         follow_info->carray_bt = radio_bt;
422
423         /* Raw radio button */
424         radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_group
425                                             (GTK_RADIO_BUTTON(radio_bt)),
426                                             "Raw");
427         gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"Raw\" (binary) format. "
428         "As this contains non printable characters, the screen output will be in ASCII format", NULL);
429         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt), FALSE);
430         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, FALSE, FALSE, 0);
431         SIGNAL_CONNECT(radio_bt, "toggled", follow_charset_toggle_cb,
432                        follow_info);
433         follow_info->raw_bt = radio_bt;
434
435         /* button hbox */
436         button_hbox = gtk_hbutton_box_new();
437         gtk_box_pack_start(GTK_BOX(vbox), button_hbox, FALSE, FALSE, 0);
438         gtk_button_box_set_layout (GTK_BUTTON_BOX(button_hbox), GTK_BUTTONBOX_END);
439         gtk_button_box_set_spacing(GTK_BUTTON_BOX(button_hbox), 5);
440
441         /* Create exclude stream button */
442         button = gtk_button_new_with_label("Filter out this stream");
443         SIGNAL_CONNECT(button, "clicked", follow_filter_out_stream, follow_info);
444         gtk_tooltips_set_tip (tooltips, button,
445         "Build a display filter which cuts this stream from the capture", NULL);
446         gtk_box_pack_start(GTK_BOX(button_hbox), button, FALSE, FALSE, 0);
447
448         /* Create Close Button */
449         button = BUTTON_NEW_FROM_STOCK(GTK_STOCK_CLOSE);
450         gtk_tooltips_set_tip (tooltips, button,
451             "Close the dialog and keep the current display filter", NULL);
452         gtk_box_pack_start(GTK_BOX(button_hbox), button, FALSE, FALSE, 0);
453         GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
454
455         window_set_cancel_button(streamwindow, button, window_cancel_button_cb);
456
457         /* Tuck away the follow_info object into the window */
458         OBJECT_SET_DATA(streamwindow, E_FOLLOW_INFO_KEY, follow_info);
459
460         follow_load_text(follow_info);
461         remember_follow_info(follow_info);
462
463         data_out_file = NULL;
464
465         SIGNAL_CONNECT(streamwindow, "delete_event", window_delete_event_cb, NULL);
466         SIGNAL_CONNECT(streamwindow, "destroy", follow_destroy_cb, NULL);
467
468         /* Make sure this widget gets destroyed if we quit the main loop,
469            so that if we exit, we clean up any temporary files we have
470            for "Follow TCP Stream" windows. */
471         gtk_quit_add_destroy(gtk_main_level(), GTK_OBJECT(streamwindow));
472
473         gtk_widget_show_all(streamwindow);
474         window_present(streamwindow);
475 }
476
477 /* The destroy call back has the responsibility of
478  * unlinking the temporary file
479  * and freeing the filter_out_filter */
480 static void
481 follow_destroy_cb(GtkWidget *w, gpointer data _U_)
482 {
483         follow_info_t   *follow_info;
484         int i;
485
486         follow_info = OBJECT_GET_DATA(w, E_FOLLOW_INFO_KEY);
487         i = unlink(follow_info->data_out_filename);
488         if(i != 0) {
489                 g_warning("Follow: Couldn't remove temporary file: \"%s\", errno: %s (%u)", 
490                     follow_info->data_out_filename, strerror(errno), errno);        
491         }
492         g_free(follow_info->filter_out_filter);
493         forget_follow_info(follow_info);
494         g_free(follow_info);
495 }
496
497 /* XXX - can I emulate follow_charset_toggle_cb() instead of having
498  * 3 different functions here?
499  * That might not be a bad idea, as it might mean we only reload
500  * the window once, not twice - see follow_charset_toggle_cb()
501  * for an explanation. */
502 static void
503 follow_stream_om_both(GtkWidget *w _U_, gpointer data)
504 {
505         follow_info_t   *follow_info = data;
506         follow_info->show_stream = BOTH_HOSTS;
507         follow_load_text(follow_info);
508 }
509
510 static void
511 follow_stream_om_client(GtkWidget *w _U_, gpointer data)
512 {
513         follow_info_t   *follow_info = data;
514         follow_info->show_stream = FROM_CLIENT;
515         follow_load_text(follow_info);
516 }
517
518 static void
519 follow_stream_om_server(GtkWidget *w _U_, gpointer data)
520 {
521         follow_info_t   *follow_info = data;
522         follow_info->show_stream = FROM_SERVER;
523         follow_load_text(follow_info);
524 }
525
526
527 /* Handles the display style toggling */
528 static void
529 follow_charset_toggle_cb(GtkWidget * w _U_, gpointer data)
530 {
531         follow_info_t   *follow_info = data;
532
533         /*
534          * A radio button toggles when it goes on and when it goes
535          * off, so when you click a radio button two signals are
536          * delivered.  We only want to reprocess the display once,
537          * so we do it only when the button goes on.
538          */
539         if (GTK_TOGGLE_BUTTON(w)->active) {
540                 if (w == follow_info->ebcdic_bt)
541                         follow_info->show_type = SHOW_EBCDIC;
542                 else if (w == follow_info->hexdump_bt)
543                         follow_info->show_type = SHOW_HEXDUMP;
544                 else if (w == follow_info->carray_bt)
545                         follow_info->show_type = SHOW_CARRAY;
546                 else if (w == follow_info->ascii_bt)
547                         follow_info->show_type = SHOW_ASCII;
548                 else if (w == follow_info->raw_bt)
549                         follow_info->show_type = SHOW_RAW;
550                 follow_load_text(follow_info);
551         }
552 }
553
554 #define FLT_BUF_SIZE 1024
555
556 typedef enum {
557         FRS_OK,
558         FRS_OPEN_ERROR,
559         FRS_READ_ERROR,
560         FRS_PRINT_ERROR
561 } frs_return_t;
562
563 /*
564  * XXX - the routine pointed to by "print_line" doesn't get handed lines,
565  * it gets handed bufferfuls.  That's fine for "follow_write_raw()"
566  * and "follow_add_to_gtk_text()", but, as "follow_print_text()" calls
567  * the "print_line()" routine from "print.c", and as that routine might
568  * genuinely expect to be handed a line (if, for example, it's using
569  * some OS or desktop environment's printing API, and that API expects
570  * to be handed lines), "follow_print_text()" should probably accumulate
571  * lines in a buffer and hand them "print_line()".  (If there's a
572  * complete line in a buffer - i.e., there's nothing of the line in
573  * the previous buffer or the next buffer - it can just hand that to
574  * "print_line()" after filtering out non-printables, as an
575  * optimization.)
576  *
577  * This might or might not be the reason why C arrays display
578  * correctly but get extra blank lines very other line when printed.
579  */
580 static frs_return_t
581 follow_read_stream(follow_info_t *follow_info,
582                    gboolean (*print_line) (char *, size_t, gboolean, void *),
583                    void *arg)
584 {
585     tcp_stream_chunk    sc;
586     int                 bcount, iplen;
587     guint8              client_addr[MAX_IPADDR_LEN];
588     guint16             client_port = 0;
589     gboolean            is_server;
590     guint32             current_pos, global_client_pos = 0, global_server_pos = 0;
591     guint32             *global_pos;
592     gboolean            skip;
593     gchar               initbuf[256];
594     guint32             server_packet_count = 0;
595     guint32             client_packet_count = 0;
596     char                buffer[FLT_BUF_SIZE];
597     size_t              nchars;
598     static const gchar  hexchars[16] = "0123456789abcdef";
599
600     iplen = (follow_info->is_ipv6) ? 16 : 4;
601
602     data_out_file = eth_fopen(follow_info->data_out_filename, "rb");
603     if (data_out_file == NULL) {
604         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
605                       "Could not open temporary file %s: %s", follow_info->data_out_filename,
606                       strerror(errno));
607         return FRS_OPEN_ERROR;
608     }
609
610     while (fread(&sc, 1, sizeof(sc), data_out_file)) {
611         if (client_port == 0) {
612             memcpy(client_addr, sc.src_addr, iplen);
613             client_port = sc.src_port;
614         }
615         skip = FALSE;
616         if (memcmp(client_addr, sc.src_addr, iplen) == 0 &&
617             client_port == sc.src_port) {
618             is_server = FALSE;
619             global_pos = &global_client_pos;
620             if (follow_info->show_stream == FROM_SERVER) {
621                 skip = TRUE;
622             }
623         }
624         else {
625             is_server = TRUE;
626             global_pos = &global_server_pos;
627             if (follow_info->show_stream == FROM_CLIENT) {
628                 skip = TRUE;
629             }
630         }
631
632         while (sc.dlen > 0) {
633             bcount = (sc.dlen < FLT_BUF_SIZE) ? sc.dlen : FLT_BUF_SIZE;
634             nchars = fread(buffer, 1, bcount, data_out_file);
635             if (nchars == 0)
636                 break;
637             sc.dlen -= nchars;
638
639             if (!skip) {
640                 switch (follow_info->show_type) {
641
642                 case SHOW_EBCDIC:
643                     /* If our native arch is ASCII, call: */
644                     EBCDIC_to_ASCII(buffer, nchars);
645                     if (!(*print_line) (buffer, nchars, is_server, arg))
646                         goto print_error;
647                     break;
648
649                 case SHOW_ASCII:
650                     /* If our native arch is EBCDIC, call:
651                      * ASCII_TO_EBCDIC(buffer, nchars);
652                      */
653                     if (!(*print_line) (buffer, nchars, is_server, arg))
654                         goto print_error;
655                     break;
656
657                 case SHOW_RAW:
658                     /* Don't translate, no matter what the native arch
659                      * is.
660                      */
661                     if (!(*print_line) (buffer, nchars, is_server, arg))
662                         goto print_error;
663                     break;
664
665                 case SHOW_HEXDUMP:
666                     current_pos = 0;
667                     while (current_pos < nchars) {
668                         gchar hexbuf[256];
669                         int i;
670                         gchar *cur = hexbuf, *ascii_start;
671
672                         /* is_server indentation : put 78 spaces at the
673                          * beginning of the string */
674                         if (is_server && follow_info->show_stream == BOTH_HOSTS) {
675                             memset(cur, ' ', 78);
676                             cur += 78;
677                         }
678                         cur += g_snprintf(cur, 20, "%08X  ", *global_pos);
679                         /* 49 is space consumed by hex chars */
680                         ascii_start = cur + 49;
681                         for (i = 0; i < 16 && current_pos + i < nchars; i++) {
682                             *cur++ =
683                                 hexchars[(buffer[current_pos + i] & 0xf0) >> 4];
684                             *cur++ =
685                                 hexchars[buffer[current_pos + i] & 0x0f];
686                             *cur++ = ' ';
687                             if (i == 7)
688                                 *cur++ = ' ';
689                         }
690                         /* Fill it up if column isn't complete */
691                         while (cur < ascii_start)  
692                             *cur++ = ' ';
693
694                         /* Now dump bytes as text */
695                         for (i = 0; i < 16 && current_pos + i < nchars; i++) {
696                             *cur++ =
697                                 (isprint((guchar)buffer[current_pos + i]) ?
698                                 buffer[current_pos + i] : '.' );
699                             if (i == 7) {
700                                 *cur++ = ' ';
701                             }
702                         }
703                         current_pos += i;
704                         (*global_pos) += i;
705                         *cur++ = '\n';
706                         *cur = 0;
707                         if (!(*print_line) (hexbuf, strlen(hexbuf), is_server, arg))
708                             goto print_error;
709                     }
710                     break;
711
712                 case SHOW_CARRAY:
713                     current_pos = 0;
714                     g_snprintf(initbuf, sizeof(initbuf), "char peer%d_%d[] = {\n", 
715                             is_server ? 1 : 0, 
716                             is_server ? server_packet_count++ : client_packet_count++);
717                     if (!(*print_line) (initbuf, strlen(initbuf), is_server, arg))
718                         goto print_error;
719                     while (current_pos < nchars) {
720                         gchar hexbuf[256];
721                         int i, cur;
722
723                         cur = 0;
724                         for (i = 0; i < 8 && current_pos + i < nchars; i++) {
725                           /* Prepend entries with "0x" */
726                           hexbuf[cur++] = '0';
727                           hexbuf[cur++] = 'x';
728                             hexbuf[cur++] =
729                                 hexchars[(buffer[current_pos + i] & 0xf0) >> 4];
730                             hexbuf[cur++] =
731                                 hexchars[buffer[current_pos + i] & 0x0f];
732
733                             /* Delimit array entries with a comma */
734                             if (current_pos + i + 1 < nchars)
735                               hexbuf[cur++] = ',';
736
737                             hexbuf[cur++] = ' ';
738                         }
739
740                         /* Terminate the array if we are at the end */
741                         if (current_pos + i == nchars) {
742                             hexbuf[cur++] = '}';
743                             hexbuf[cur++] = ';';
744                         }
745
746                         current_pos += i;
747                         (*global_pos) += i;
748                         hexbuf[cur++] = '\n';
749                         hexbuf[cur] = 0;
750                         if (!(*print_line) (hexbuf, strlen(hexbuf), is_server, arg))
751                             goto print_error;
752                     }
753                     break;
754                 }
755             }
756         }
757     }
758     if (ferror(data_out_file)) {
759         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
760                       "Error reading temporary file %s: %s", follow_info->data_out_filename,
761                       strerror(errno));
762         fclose(data_out_file);
763         data_out_file = NULL;
764         return FRS_READ_ERROR;
765     }
766
767         fclose(data_out_file);
768         data_out_file = NULL;
769     return FRS_OK;
770
771 print_error:
772     fclose(data_out_file);
773     data_out_file = NULL;
774     return FRS_PRINT_ERROR;
775 }
776
777 /*
778  * XXX - for text printing, we probably want to wrap lines at 80 characters;
779  * (PostScript printing is doing this already), and perhaps put some kind of 
780  * dingbat (to use the technical term) to indicate a wrapped line, along the 
781  * lines of what's done when displaying this in a window, as per Warren Young's 
782  * suggestion.
783  */
784 static gboolean
785 follow_print_text(char *buffer, size_t nchars, gboolean is_server _U_, void *arg)
786 {
787     print_stream_t *stream = arg;
788     size_t i;
789     char *str;
790
791     /* convert non printable characters */
792     for (i = 0; i < nchars; i++) {
793         if (buffer[i] == '\n' || buffer[i] == '\r')
794             continue;
795         if (! isprint((guchar)buffer[i])) {
796             buffer[i] = '.';
797         }
798     }
799
800     /* convert unterminated char array to a zero terminated string */
801     str = g_malloc(nchars + 1);
802     memcpy(str, buffer, nchars);
803     str[nchars] = 0;
804     print_line(stream, /*indent*/ 0, str);
805     g_free(str);
806
807     return TRUE;
808 }
809
810 static gboolean
811 follow_write_raw(char *buffer, size_t nchars, gboolean is_server _U_, void *arg)
812 {
813     FILE *fh = arg;
814     size_t nwritten;
815
816     nwritten = fwrite(buffer, 1, nchars, fh);
817     if (nwritten != nchars)
818         return FALSE;
819
820     return TRUE;
821 }
822
823 static void
824 follow_filter_out_stream(GtkWidget * w _U_, gpointer data)
825 {
826     follow_info_t       *follow_info = data;
827
828     /* Lock out user from messing with us. (ie. don't free our data!) */
829     gtk_widget_set_sensitive(follow_info->streamwindow, FALSE);
830
831     /* Set the display filter. */
832     gtk_entry_set_text(GTK_ENTRY(follow_info->filter_te), follow_info->filter_out_filter);
833
834     /* Run the display filter so it goes in effect. */
835     main_filter_packets(&cfile, follow_info->filter_out_filter, FALSE);
836
837     /* we force a subsequent close */
838     window_destroy(follow_info->streamwindow);
839
840     return;
841 }
842
843 static void
844 follow_print_stream(GtkWidget * w _U_, gpointer data)
845 {
846     print_stream_t      *stream;
847     gboolean            to_file;
848     char                *print_dest;
849     follow_info_t       *follow_info = data;
850 #ifdef _WIN32
851     gboolean win_printer = FALSE;
852 #endif
853
854     switch (prefs.pr_dest) {
855     case PR_DEST_CMD:
856 #ifdef _WIN32
857         win_printer = TRUE;
858         /*XXX should use temp file stuff in util routines */
859         print_dest = g_strdup(tmpnam(NULL));
860         to_file = TRUE;
861 #else
862         print_dest = prefs.pr_cmd;
863         to_file = FALSE;
864 #endif
865         break;
866     case PR_DEST_FILE:
867         print_dest = prefs.pr_file;
868         to_file = TRUE;
869         break;
870     default:                    /* "Can't happen" */
871         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
872             "Couldn't figure out where to send the print "
873             "job. Check your preferences.");
874         return;
875     }
876
877     switch (prefs.pr_format) {
878
879     case PR_FMT_TEXT:
880       stream = print_stream_text_new(to_file, print_dest);
881       break;
882
883     case PR_FMT_PS:
884       stream = print_stream_ps_new(to_file, print_dest);
885       break;
886
887     default:
888       g_assert_not_reached();
889       stream = NULL;
890     }
891     if (stream == NULL) {
892         if (to_file) {
893             open_failure_alert_box(prefs.pr_file, errno, TRUE);
894         } else {
895             simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
896                       "Couldn't run print command %s.", prefs.pr_cmd);
897         }
898         return;
899     }
900
901     if (!print_preamble(stream, cfile.filename))
902         goto print_error;
903
904     switch (follow_read_stream(follow_info, follow_print_text, stream)) {
905     case FRS_OK:
906         break;
907     case FRS_OPEN_ERROR:
908     case FRS_READ_ERROR:
909             /* XXX - cancel printing? */
910             destroy_print_stream(stream);
911             return;
912     case FRS_PRINT_ERROR:
913             goto print_error;
914     }
915
916     if (!print_finale(stream))
917             goto print_error;
918
919     if (!destroy_print_stream(stream)) {
920         if (to_file) {
921           write_failure_alert_box(prefs.pr_file, errno);
922         } else {
923             simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
924                           "Error closing print destination.");
925         }
926     }
927 #ifdef _WIN32
928     if (win_printer) {
929         print_mswin(print_dest);
930
931         /* trash temp file */
932         eth_remove(print_dest);
933     }
934 #endif
935     return;
936
937 print_error:
938     if (to_file) {
939         write_failure_alert_box(prefs.pr_file, errno);
940     } else {
941         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
942           "Error writing to print command: %s", strerror(errno));
943     }
944     /* XXX - cancel printing? */
945     destroy_print_stream(stream);
946
947 #ifdef _WIN32
948     if (win_printer) {
949         /* trash temp file */
950         eth_remove(print_dest);
951     }
952 #endif
953 }
954
955 /* static variable declarations to speed up the performance
956  * of follow_load_text and follow_add_to_gtk_text
957  */
958 static GdkColor server_fg, server_bg;
959 static GdkColor client_fg, client_bg;
960 #if GTK_MAJOR_VERSION >= 2
961 static GtkTextTag *server_tag, *client_tag;
962 #endif
963
964 static gboolean
965 follow_add_to_gtk_text(char *buffer, size_t nchars, gboolean is_server,
966                        void *arg)
967 {
968     GtkWidget *text = arg;
969 #if GTK_MAJOR_VERSION >= 2
970     GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
971     GtkTextIter    iter;
972 #endif
973
974 #if GTK_MAJOR_VERSION >= 2 || GTK_MINOR_VERSION >= 3
975     /* While our isprint() hack is in place, we
976      * have to use convert some chars to '.' in order
977      * to be able to see the data we *should* see
978      * in the GtkText widget.
979      */
980     size_t i;
981
982     for (i = 0; i < nchars; i++) {
983         if (buffer[i] == '\n' || buffer[i] == '\r')
984             continue;
985         if (! isprint(buffer[i])) {
986             buffer[i] = '.';
987         }
988     }
989 #endif
990
991 #if GTK_MAJOR_VERSION < 2
992     if (is_server) {
993         gtk_text_insert(GTK_TEXT(text), user_font_get_regular(), &server_fg, 
994                         &server_bg, buffer, nchars);
995     } else {
996         gtk_text_insert(GTK_TEXT(text), user_font_get_regular(), &client_fg, 
997                         &client_bg, buffer, nchars);
998     }
999 #else
1000     gtk_text_buffer_get_end_iter(buf, &iter);
1001     if (is_server) {
1002         gtk_text_buffer_insert_with_tags(buf, &iter, buffer, nchars, 
1003                                         server_tag, NULL);
1004     } else {
1005         gtk_text_buffer_insert_with_tags(buf, &iter, buffer, nchars, 
1006                                         client_tag, NULL);
1007     }
1008 #endif
1009     return TRUE;
1010 }
1011
1012 static void
1013 follow_load_text(follow_info_t *follow_info)
1014 {
1015 #if GTK_MAJOR_VERSION < 2
1016     int bytes_already;
1017 #else
1018     GtkTextBuffer *buf;
1019
1020     buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(follow_info->text));
1021 #endif
1022
1023     /* prepare colors one time for repeated use by follow_add_to_gtk_text */
1024     color_t_to_gdkcolor(&server_fg, &prefs.st_server_fg);
1025     color_t_to_gdkcolor(&server_bg, &prefs.st_server_bg);
1026     color_t_to_gdkcolor(&client_fg, &prefs.st_client_fg);
1027     color_t_to_gdkcolor(&client_bg, &prefs.st_client_bg);
1028
1029     /* Delete any info already in text box */
1030 #if GTK_MAJOR_VERSION < 2
1031     bytes_already = gtk_text_get_length(GTK_TEXT(follow_info->text));
1032     if (bytes_already > 0) {
1033         gtk_text_set_point(GTK_TEXT(follow_info->text), 0);
1034         gtk_text_forward_delete(GTK_TEXT(follow_info->text), bytes_already);
1035     }
1036
1037     /* stop the updates while we fill the text box */
1038     gtk_text_freeze(GTK_TEXT(follow_info->text));
1039 #else
1040     /* prepare tags one time for repeated use by follow_add_to_gtk_text */
1041     server_tag = gtk_text_buffer_create_tag(buf, NULL, "foreground-gdk", &server_fg,
1042                                      "background-gdk", &server_bg, "font-desc",
1043                                      user_font_get_regular(), NULL);
1044     client_tag = gtk_text_buffer_create_tag(buf, NULL, "foreground-gdk", &client_fg,
1045                                      "background-gdk", &client_bg, "font-desc",
1046                                      user_font_get_regular(), NULL);
1047
1048     gtk_text_buffer_set_text(buf, "", -1);
1049 #endif
1050     follow_read_stream(follow_info, follow_add_to_gtk_text, follow_info->text);
1051 #if GTK_MAJOR_VERSION < 2
1052     gtk_text_thaw(GTK_TEXT(follow_info->text));
1053 #endif
1054 }
1055
1056
1057 /*
1058  * Keep a static pointer to the current "Save TCP Follow Stream As" window, if
1059  * any, so that if somebody tries to do "Save"
1060  * while there's already a "Save TCP Follow Stream" window up, we just pop
1061  * up the existing one, rather than creating a new one.
1062  */
1063 static void
1064 follow_save_as_cmd_cb(GtkWidget *w _U_, gpointer data)
1065 {
1066     GtkWidget           *new_win;
1067     follow_info_t       *follow_info = data;
1068
1069     if (follow_info->follow_save_as_w != NULL) {
1070         /* There's already a dialog box; reactivate it. */
1071         reactivate_window(follow_info->follow_save_as_w);
1072         return;
1073     }
1074
1075     new_win = file_selection_new("Ethereal: Save TCP Follow Stream As",
1076                                  FILE_SELECTION_SAVE);
1077     follow_info->follow_save_as_w = new_win;
1078
1079     /* Tuck away the follow_info object into the window */
1080     OBJECT_SET_DATA(new_win, E_FOLLOW_INFO_KEY, follow_info);
1081
1082     SIGNAL_CONNECT(new_win, "destroy", follow_save_as_destroy_cb, follow_info);
1083
1084 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
1085     if (gtk_dialog_run(GTK_DIALOG(new_win)) == GTK_RESPONSE_ACCEPT)
1086     {
1087         follow_save_as_ok_cb(new_win, new_win);
1088     } else {
1089         window_destroy(new_win);
1090     }
1091 #else
1092     /* Connect the ok_button to file_save_as_ok_cb function and pass along a
1093        pointer to the file selection box widget */
1094     SIGNAL_CONNECT(GTK_FILE_SELECTION(new_win)->ok_button, 
1095         "clicked", follow_save_as_ok_cb, new_win);
1096
1097     window_set_cancel_button(new_win, 
1098         GTK_FILE_SELECTION(new_win)->cancel_button, window_cancel_button_cb);
1099
1100     gtk_file_selection_set_filename(GTK_FILE_SELECTION(new_win), "");
1101
1102     SIGNAL_CONNECT(new_win, "delete_event", window_delete_event_cb, NULL);
1103
1104     gtk_widget_show_all(new_win);
1105     window_present(new_win);
1106 #endif
1107 }
1108
1109
1110 static void
1111 follow_save_as_ok_cb(GtkWidget * w _U_, gpointer fs)
1112 {
1113     gchar               *to_name;
1114     follow_info_t       *follow_info;
1115     FILE                *fh;
1116     print_stream_t      *stream = NULL;
1117     gchar               *dirname;
1118
1119 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
1120     to_name = g_strdup(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs)));
1121 #else
1122     to_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(fs)));
1123 #endif
1124
1125     /* Perhaps the user specified a directory instead of a file.
1126        Check whether they did. */
1127     if (test_for_directory(to_name) == EISDIR) {
1128         /* It's a directory - set the file selection box to display that
1129            directory, and leave the selection box displayed. */
1130         set_last_open_dir(to_name);
1131         g_free(to_name);
1132         file_selection_set_current_folder(fs, get_last_open_dir());
1133         return;
1134     }
1135
1136     follow_info = OBJECT_GET_DATA(fs, E_FOLLOW_INFO_KEY);
1137     if (follow_info->show_type == SHOW_RAW) {
1138         /* Write the data out as raw binary data */
1139         fh = eth_fopen(to_name, "wb");
1140     } else {
1141         /* Write it out as text */
1142         fh = eth_fopen(to_name, "w");
1143     }
1144     if (fh == NULL) {
1145         open_failure_alert_box(to_name, errno, TRUE);
1146         g_free(to_name);
1147         return;
1148     }
1149
1150     gtk_widget_hide(GTK_WIDGET(fs));
1151     window_destroy(GTK_WIDGET(fs));
1152
1153     if (follow_info->show_type == SHOW_RAW) {
1154         switch (follow_read_stream(follow_info, follow_write_raw, fh)) {
1155         case FRS_OK:
1156             if (fclose(fh) == EOF)
1157                 write_failure_alert_box(to_name, errno);
1158             break;
1159
1160         case FRS_OPEN_ERROR:
1161         case FRS_READ_ERROR:
1162             fclose(fh);
1163             break;
1164
1165         case FRS_PRINT_ERROR:
1166             write_failure_alert_box(to_name, errno);
1167             fclose(fh);
1168             break;
1169         }
1170     } else {
1171         stream = print_stream_text_stdio_new(fh);
1172         switch (follow_read_stream(follow_info, follow_print_text, stream)) {
1173         case FRS_OK:
1174             if (!destroy_print_stream(stream))
1175                 write_failure_alert_box(to_name, errno);
1176             break;
1177
1178         case FRS_OPEN_ERROR:
1179         case FRS_READ_ERROR:
1180             destroy_print_stream(stream);
1181             break;
1182
1183         case FRS_PRINT_ERROR:
1184             write_failure_alert_box(to_name, errno);
1185             destroy_print_stream(stream);
1186             break;
1187         }
1188     }
1189
1190     /* Save the directory name for future file dialogs. */
1191     dirname = get_dirname(to_name);  /* Overwrites to_name */
1192     set_last_open_dir(dirname);
1193     g_free(to_name);
1194 }
1195
1196 static void
1197 follow_save_as_destroy_cb(GtkWidget * win _U_, gpointer data)
1198 {
1199         follow_info_t   *follow_info = data;
1200
1201         /* Note that we no longer have a dialog box. */
1202         follow_info->follow_save_as_w = NULL;
1203 }