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