Replace all strerror() with g_strerror().
[obnox/wireshark/wip.git] / gtk / follow_stream.c
1 /* follow_stream.c
2  * Common routines for following data streams
3  *
4  * $Id$
5  *
6  * Wireshark - Network traffic analyzer
7  * By Gerald Combs <gerald@wireshark.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
23  * USA.
24  */
25
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29
30 #ifdef HAVE_UNISTD_H
31 #include <unistd.h>
32 #endif
33 #include <ctype.h>
34 #include <string.h>
35
36 #include <gtk/gtk.h>
37
38 #include <epan/addr_resolv.h>
39 #include <epan/follow.h>
40 #include <epan/filesystem.h>
41 #include <epan/prefs.h>
42 #include <epan/charsets.h>
43
44 #include <../alert_box.h>
45 #include <../isprint.h>
46 #include <../print.h>
47 #include <../simple_dialog.h>
48 #include <wsutil/file_util.h>
49
50 #include <gtk/color_utils.h>
51 #include <gtk/stock_icons.h>
52 #include <gtk/dlg_utils.h>
53 #include <gtk/follow_stream.h>
54 #include <gtk/font_utils.h>
55 #include <gtk/file_dlg.h>
56 #include <gtk/gui_utils.h>
57 #include <gtk/help_dlg.h>
58 #include "gtk/main.h"
59
60 #ifdef _WIN32
61 #include "../tempfile.h"
62 #include "win32/print_win32.h"
63 #endif
64
65 /* static variable declarations to speed up the performance
66  * of follow_load_text and follow_add_to_gtk_text
67  */
68 static GdkColor server_fg, server_bg;
69 static GdkColor client_fg, client_bg;
70 static GtkTextTag *server_tag, *client_tag;
71
72 static void follow_find_destroy_cb(GtkWidget * win _U_, gpointer data);
73 static void follow_find_button_cb(GtkWidget * w, gpointer data);
74 static gboolean follow_save_as_ok_cb(GtkWidget * w _U_, gpointer fs);
75 static void follow_destroy_cb(GtkWidget *w, gpointer data _U_);
76 static void follow_save_as_destroy_cb(GtkWidget * win _U_, gpointer data);
77
78 GList *follow_infos = NULL;
79
80 static frs_return_t
81 follow_read_stream(follow_info_t *follow_info,
82                    gboolean (*print_line_fcn_p)(char *, size_t, gboolean, void *),
83                    void *arg)
84 {
85         switch(follow_info->follow_type) {
86
87         case FOLLOW_TCP :
88                 return follow_read_tcp_stream(follow_info, print_line_fcn_p, arg);
89
90         case FOLLOW_UDP :
91                 return follow_read_udp_stream(follow_info, print_line_fcn_p, arg);
92
93         case FOLLOW_SSL :
94                 return follow_read_ssl_stream(follow_info, print_line_fcn_p, arg);
95
96         default :
97                 g_assert_not_reached();
98                 return 0;
99         }
100 }
101
102 gboolean
103 follow_add_to_gtk_text(char *buffer, size_t nchars, gboolean is_server,
104                        void *arg)
105 {
106         GtkWidget *text = arg;
107         GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
108         GtkTextIter    iter;
109
110         /* While our isprint() hack is in place, we
111          * have to convert some chars to '.' in order
112          * to be able to see the data we *should* see
113          * in the GtkText widget.
114          */
115         size_t i;
116
117         for (i = 0; i < nchars; i++) {
118                 if (buffer[i] == '\n' || buffer[i] == '\r')
119                         continue;
120                 if (! isprint((guchar)buffer[i])) {
121                         buffer[i] = '.';
122                 }
123         }
124
125         gtk_text_buffer_get_end_iter(buf, &iter);
126         if (is_server) {
127                 gtk_text_buffer_insert_with_tags(buf, &iter, buffer, (gint) nchars,
128                                                  server_tag, NULL);
129         } else {
130                 gtk_text_buffer_insert_with_tags(buf, &iter, buffer, (gint) nchars,
131                                                  client_tag, NULL);
132         }
133         return TRUE;
134 }
135
136 /*
137  * XXX - for text printing, we probably want to wrap lines at 80 characters;
138  * (PostScript printing is doing this already), and perhaps put some kind of
139  * dingbat (to use the technical term) to indicate a wrapped line, along the
140  * lines of what's done when displaying this in a window, as per Warren Young's
141  * suggestion.
142  */
143 static gboolean
144 follow_print_text(char *buffer, size_t nchars, gboolean is_server _U_,
145                   void *arg)
146 {
147         print_stream_t *stream = arg;
148         size_t i;
149         char *str;
150
151         /* convert non printable characters */
152         for (i = 0; i < nchars; i++) {
153                 if (buffer[i] == '\n' || buffer[i] == '\r')
154                         continue;
155                 if (! isprint((guchar)buffer[i])) {
156                         buffer[i] = '.';
157                 }
158         }
159
160         /* convert unterminated char array to a zero terminated string */
161         str = g_malloc(nchars + 1);
162         memcpy(str, buffer, nchars);
163         str[nchars] = 0;
164         print_line(stream, /*indent*/ 0, str);
165         g_free(str);
166
167         return TRUE;
168 }
169
170 static gboolean
171 follow_write_raw(char *buffer, size_t nchars, gboolean is_server _U_, void *arg)
172 {
173         FILE *fh = arg;
174         size_t nwritten;
175
176         nwritten = fwrite(buffer, 1, nchars, fh);
177         if (nwritten != nchars)
178                 return FALSE;
179
180         return TRUE;
181 }
182
183 /* Handles the display style toggling */
184 static void
185 follow_charset_toggle_cb(GtkWidget * w _U_, gpointer data)
186 {
187         follow_info_t   *follow_info = data;
188
189         /*
190          * A radio button toggles when it goes on and when it goes
191          * off, so when you click a radio button two signals are
192          * delivered.  We only want to reprocess the display once,
193          * so we do it only when the button goes on.
194          */
195         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w))) {
196                 if (w == follow_info->ebcdic_bt)
197                         follow_info->show_type = SHOW_EBCDIC;
198                 else if (w == follow_info->hexdump_bt)
199                         follow_info->show_type = SHOW_HEXDUMP;
200                 else if (w == follow_info->carray_bt)
201                         follow_info->show_type = SHOW_CARRAY;
202                 else if (w == follow_info->ascii_bt)
203                         follow_info->show_type = SHOW_ASCII;
204                 else if (w == follow_info->raw_bt)
205                         follow_info->show_type = SHOW_RAW;
206                 follow_load_text(follow_info);
207         }
208 }
209
210 void
211 follow_load_text(follow_info_t *follow_info)
212 {
213         GtkTextBuffer *buf;
214
215         buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(follow_info->text));
216
217         /* prepare colors one time for repeated use by follow_add_to_gtk_text */
218         color_t_to_gdkcolor(&server_fg, &prefs.st_server_fg);
219         color_t_to_gdkcolor(&server_bg, &prefs.st_server_bg);
220         color_t_to_gdkcolor(&client_fg, &prefs.st_client_fg);
221         color_t_to_gdkcolor(&client_bg, &prefs.st_client_bg);
222
223         /* prepare tags one time for repeated use by follow_add_to_gtk_text */
224         server_tag = gtk_text_buffer_create_tag(buf, NULL, "foreground-gdk",
225                                                 &server_fg, "background-gdk",
226                                                 &server_bg, "font-desc",
227                                                 user_font_get_regular(), NULL);
228         client_tag = gtk_text_buffer_create_tag(buf, NULL, "foreground-gdk",
229                                                 &client_fg, "background-gdk",
230                                                 &client_bg, "font-desc",
231                                                 user_font_get_regular(), NULL);
232
233         /* Delete any info already in text box */
234         gtk_text_buffer_set_text(buf, "", -1);
235
236         follow_read_stream(follow_info, follow_add_to_gtk_text,
237                            follow_info->text);
238 }
239
240 void
241 follow_filter_out_stream(GtkWidget * w _U_, gpointer data)
242 {
243         follow_info_t   *follow_info = data;
244
245         /* Lock out user from messing with us. (ie. don't free our data!) */
246         gtk_widget_set_sensitive(follow_info->streamwindow, FALSE);
247
248         /* Set the display filter. */
249         gtk_entry_set_text(GTK_ENTRY(follow_info->filter_te),
250                            follow_info->filter_out_filter);
251
252         /* Run the display filter so it goes in effect. */
253         main_filter_packets(&cfile, follow_info->filter_out_filter, FALSE);
254
255         /* we force a subsequent close */
256         window_destroy(follow_info->streamwindow);
257
258         return;
259 }
260
261 static void
262 follow_find_cb(GtkWidget * w _U_, gpointer data)
263 {
264         follow_info_t           *follow_info = data;
265         GtkTooltips             *tooltips;
266         GtkWidget               *find_dlg_w, *main_vb, *buttons_row, *find_lb;
267         GtkWidget               *find_hb, *find_text_box, *find_bt, *cancel_bt;
268
269         tooltips = gtk_tooltips_new();
270
271         if (follow_info->find_dlg_w != NULL) {
272                 /* There's already a dialog box; reactivate it. */
273                 reactivate_window(follow_info->find_dlg_w);
274                 return;
275         }
276
277         /* Create the find box */
278         find_dlg_w = dlg_window_new("Wireshark: Find text");
279         gtk_window_set_transient_for(GTK_WINDOW(find_dlg_w),
280                                      GTK_WINDOW(follow_info->streamwindow));
281         gtk_window_set_destroy_with_parent(GTK_WINDOW(find_dlg_w), TRUE);
282         follow_info->find_dlg_w = find_dlg_w;
283
284         g_signal_connect(find_dlg_w, "destroy", G_CALLBACK(follow_find_destroy_cb),
285                        follow_info);
286         g_signal_connect(find_dlg_w, "delete_event", G_CALLBACK(window_delete_event_cb),
287                        NULL);
288
289         /* Main vertical box */
290         main_vb = gtk_vbox_new(FALSE, 3);
291         gtk_container_set_border_width(GTK_CONTAINER(main_vb), 5);
292         gtk_container_add(GTK_CONTAINER(find_dlg_w), main_vb);
293
294         /* Horizontal box for find label, entry field and up/down radio
295            buttons */
296         find_hb = gtk_hbox_new(FALSE, 3);
297         gtk_container_add(GTK_CONTAINER(main_vb), find_hb);
298         gtk_widget_show(find_hb);
299
300         /* Find label */
301         find_lb = gtk_label_new("Find text:");
302         gtk_box_pack_start(GTK_BOX(find_hb), find_lb, FALSE, FALSE, 0);
303         gtk_widget_show(find_lb);
304
305         /* Find field */
306         find_text_box = gtk_entry_new();
307         gtk_box_pack_start(GTK_BOX(find_hb), find_text_box, FALSE, FALSE, 0);
308         gtk_tooltips_set_tip(tooltips, find_text_box, "Text to search for (case sensitive)", NULL);
309         gtk_widget_show(find_text_box);
310
311         /* Buttons row */
312         buttons_row = dlg_button_row_new(GTK_STOCK_FIND, GTK_STOCK_CANCEL,
313                                          NULL);
314         gtk_container_add(GTK_CONTAINER(main_vb), buttons_row);
315         find_bt = g_object_get_data(G_OBJECT(buttons_row), GTK_STOCK_FIND);
316         cancel_bt = g_object_get_data(G_OBJECT(buttons_row), GTK_STOCK_CANCEL);
317
318         g_signal_connect(find_bt, "clicked", G_CALLBACK(follow_find_button_cb), follow_info);
319         g_object_set_data(G_OBJECT(find_bt), "find_string", find_text_box);
320         window_set_cancel_button(find_dlg_w, cancel_bt,
321                                  window_cancel_button_cb);
322
323         /* Hitting return in the find field "clicks" the find button */
324         dlg_set_activate(find_text_box, find_bt);
325
326         /* Show the dialog */
327         gtk_widget_show_all(find_dlg_w);
328         window_present(find_dlg_w);
329 }
330
331 static void
332 follow_find_button_cb(GtkWidget * w, gpointer data)
333 {
334         gboolean                found;
335         const gchar             *find_string;
336         follow_info_t   *follow_info = data;
337         GtkTextBuffer   *buffer;
338         GtkTextIter             iter, match_start, match_end;
339         GtkTextMark             *last_pos_mark;
340         GtkWidget               *find_string_w;
341
342         /* Get the text the user typed into the find field */
343         find_string_w = (GtkWidget *)g_object_get_data(G_OBJECT(w), "find_string");
344         find_string = gtk_entry_get_text(GTK_ENTRY(find_string_w));
345
346         /* Get the buffer associated with the follow stream */
347         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(follow_info->text));
348         gtk_text_buffer_get_start_iter(buffer, &iter);
349
350         /* Look for the search string in the buffer */
351         last_pos_mark = gtk_text_buffer_get_mark(buffer, "last_position");
352         if(last_pos_mark)
353                 gtk_text_buffer_get_iter_at_mark(buffer, &iter, last_pos_mark);
354
355         found = gtk_text_iter_forward_search(&iter, find_string, 0,
356                                              &match_start,
357                                              &match_end,
358                                              NULL);
359
360         if(found) {
361                 gtk_text_buffer_select_range(buffer, &match_start, &match_end);
362                 last_pos_mark = gtk_text_buffer_create_mark (buffer,
363                                                              "last_position",
364                                                              &match_end, FALSE);
365                 gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(follow_info->text), last_pos_mark);
366         } else {
367                 /* We didn't find a match */
368                 simple_dialog(ESD_TYPE_INFO, ESD_BTN_OK,
369                               "%sFind text has reached the end of the followed "
370                               "stream%s\n\nThe next search will start from the "
371                               "beginning", simple_dialog_primary_start(),
372                               simple_dialog_primary_end());
373                 if(last_pos_mark)
374                         gtk_text_buffer_delete_mark(buffer, last_pos_mark);
375         }
376
377 }
378
379 static void
380 follow_find_destroy_cb(GtkWidget * win _U_, gpointer data)
381 {
382         follow_info_t   *follow_info = data;
383
384         /* Note that we no longer have a dialog box. */
385         follow_info->find_dlg_w = NULL;
386 }
387
388 static void
389 follow_print_stream(GtkWidget * w _U_, gpointer data)
390 {
391         print_stream_t  *stream;
392         gboolean         to_file;
393         char            *print_dest;
394         follow_info_t   *follow_info = data;
395 #ifdef _WIN32
396         gboolean         win_printer = FALSE;
397         int              tmp_fd;
398         char             *tmp_namebuf;
399 #endif
400
401         switch (prefs.pr_dest) {
402         case PR_DEST_CMD:
403 #ifdef _WIN32
404                 win_printer = TRUE;
405                 /* (The code for creating a temp filename is adapted from print_dlg.c).   */
406                 /* We currently don't have a function in util.h to create just a tempfile */
407                 /* name, so simply create a tempfile using the "official" function,       */
408                 /* then delete this file again. After this, the name MUST be available.   */
409                 /* */
410                 /* Don't use tmpnam() or such, as this will fail under some ACL           */
411                 /* circumstances: http://bugs.wireshark.org/bugzilla/show_bug.cgi?id=358  */
412                 /* Also: tmpnam is "insecure" and should not be used.                     */
413                 tmp_fd = create_tempfile(&tmp_namebuf, "wshprint");
414                 if(tmp_fd == -1) {
415                         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
416                                       "Couldn't create temporary file for printing:\n%s", tmp_namebuf);
417                         return;
418                 }
419                 ws_close(tmp_fd);
420                 ws_unlink(tmp_namebuf);
421                 print_dest = tmp_namebuf;
422                 to_file = TRUE;
423 #else
424                 print_dest = prefs.pr_cmd;
425                 to_file = FALSE;
426 #endif
427                 break;
428         case PR_DEST_FILE:
429                 print_dest = prefs.pr_file;
430                 to_file = TRUE;
431                 break;
432         default:                        /* "Can't happen" */
433                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
434                               "Couldn't figure out where to send the print "
435                               "job. Check your preferences.");
436                 return;
437         }
438
439         switch (prefs.pr_format) {
440
441         case PR_FMT_TEXT:
442                 stream = print_stream_text_new(to_file, print_dest);
443                 break;
444
445         case PR_FMT_PS:
446                 stream = print_stream_ps_new(to_file, print_dest);
447                 break;
448
449         default:
450                 g_assert_not_reached();
451                 stream = NULL;
452         }
453         if (stream == NULL) {
454                 if (to_file) {
455                         open_failure_alert_box(print_dest, errno, TRUE);
456                 } else {
457                         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
458                                       "Couldn't run print command %s.",
459                                       prefs.pr_cmd);
460                 }
461                 return;
462         }
463
464         if (!print_preamble(stream, cfile.filename))
465                 goto print_error;
466
467         switch (follow_read_stream(follow_info, follow_print_text, stream)) {
468         case FRS_OK:
469                 break;
470         case FRS_OPEN_ERROR:
471         case FRS_READ_ERROR:
472                 /* XXX - cancel printing? */
473                 destroy_print_stream(stream);
474                 return;
475         case FRS_PRINT_ERROR:
476                 goto print_error;
477         }
478
479         if (!print_finale(stream))
480                 goto print_error;
481
482         if (!destroy_print_stream(stream)) {
483                 if (to_file) {
484                         write_failure_alert_box(print_dest, errno);
485                 } else {
486                         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
487                                       "Error closing print destination.");
488                 }
489         }
490 #ifdef _WIN32
491         if (win_printer) {
492                 print_mswin(print_dest);
493
494                 /* trash temp file */
495                 ws_remove(print_dest);
496         }
497 #endif
498         return;
499
500  print_error:
501         if (to_file) {
502                 write_failure_alert_box(print_dest, errno);
503         } else {
504                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
505                               "Error writing to print command: %s",
506                               g_strerror(errno));
507         }
508         /* XXX - cancel printing? */
509         destroy_print_stream(stream);
510
511 #ifdef _WIN32
512         if (win_printer) {
513                 /* trash temp file */
514                 ws_remove(print_dest);
515         }
516 #endif
517 }
518
519 /*
520  * Keep a static pointer to the current "Save Follow Stream As" window, if
521  * any, so that if somebody tries to do "Save"
522  * while there's already a "Save Follow Stream" window up, we just pop
523  * up the existing one, rather than creating a new one.
524  */
525
526 static void
527 follow_save_as_cmd_cb(GtkWidget *w _U_, gpointer data)
528 {
529         GtkWidget               *new_win;
530         follow_info_t   *follow_info = data;
531
532 #if 0  /* XXX: GtkFileChooserDialog/gtk_dialog_run currently being used is effectively modal so this is not req'd */
533         if (follow_info->follow_save_as_w != NULL) {
534                 /* There's already a dialog box; reactivate it. */
535                 reactivate_window(follow_info->follow_save_as_w);
536                 return;
537         }
538 #endif
539         new_win = file_selection_new("Wireshark: Save Follow Stream As",
540                                      FILE_SELECTION_SAVE);
541         follow_info->follow_save_as_w = new_win;
542 #if GTK_CHECK_VERSION(2,8,0)
543         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(new_win), TRUE);
544 #endif
545
546         /* Tuck away the follow_info object into the window */
547         g_object_set_data(G_OBJECT(new_win), E_FOLLOW_INFO_KEY, follow_info);
548
549         g_signal_connect(new_win, "destroy", G_CALLBACK(follow_save_as_destroy_cb),
550                        follow_info);
551
552 #if 0
553         if (gtk_dialog_run(GTK_DIALOG(new_win)) == GTK_RESPONSE_ACCEPT)
554                 {
555                         follow_save_as_ok_cb(new_win, new_win);
556                 } else {
557                 window_destroy(new_win);
558         }
559 #endif
560         /* "Run" the GtkFileChooserDialog.                                              */
561         /* Upon exit: If "Accept" run the OK callback.                                  */
562         /*            If the OK callback returns with a FALSE status, re-run the dialog.*/
563         /*            If not accept (ie: cancel) destroy the window.                    */
564         /* XXX: If the OK callback pops up an alert box (eg: for an error) it *must*    */
565         /*      return with a TRUE status so that the dialog window will be destroyed.  */
566         /*      Trying to re-run the dialog after popping up an alert box will not work */
567         /*       since the user will not be able to dismiss the alert box.              */
568         /*      The (somewhat unfriendly) effect: the user must re-invoke the           */
569         /*      GtkFileChooserDialog whenever the OK callback pops up an alert box.     */
570         /*                                                                              */
571         /*      ToDo: use GtkFileChooserWidget in a dialog window instead of            */
572         /*            GtkFileChooserDialog.                                             */
573         while (gtk_dialog_run(GTK_DIALOG(new_win)) == GTK_RESPONSE_ACCEPT) {
574                 if (follow_save_as_ok_cb(NULL, new_win)) {
575                     break; /* we're done */
576                 }
577         }
578         window_destroy(new_win);
579 }
580
581
582 static gboolean
583 follow_save_as_ok_cb(GtkWidget * w _U_, gpointer fs)
584 {
585         gchar           *to_name;
586         follow_info_t   *follow_info;
587         FILE            *fh;
588         print_stream_t  *stream = NULL;
589         gchar           *dirname;
590
591         to_name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs));
592
593         /* Perhaps the user specified a directory instead of a file.
594            Check whether they did. */
595         if (test_for_directory(to_name) == EISDIR) {
596                 /* It's a directory - set the file selection box to display that
597                    directory, and leave the selection box displayed. */
598                 set_last_open_dir(to_name);
599                 g_free(to_name);
600                 file_selection_set_current_folder(fs, get_last_open_dir());
601                 gtk_file_chooser_set_current_name(fs, "");
602                 return FALSE; /* do gtk_dialog_run again */
603         }
604
605         follow_info = g_object_get_data(G_OBJECT(fs), E_FOLLOW_INFO_KEY);
606
607         if (follow_info->show_type == SHOW_RAW) {
608                 /* Write the data out as raw binary data */
609                 fh = ws_fopen(to_name, "wb");
610         } else {
611                 /* Write it out as text */
612                 fh = ws_fopen(to_name, "w");
613         }
614         if (fh == NULL) {
615                 open_failure_alert_box(to_name, errno, TRUE);
616                 g_free(to_name);
617                 return TRUE;
618         }
619
620 #if 0 /* handled by caller (for now) .... */
621         gtk_widget_hide(GTK_WIDGET(fs));
622         window_destroy(GTK_WIDGET(fs));
623 #endif
624         if (follow_info->show_type == SHOW_RAW) {
625                 switch (follow_read_stream(follow_info, follow_write_raw, fh)) {
626                 case FRS_OK:
627                         if (fclose(fh) == EOF)
628                                 write_failure_alert_box(to_name, errno);
629                         break;
630
631                 case FRS_OPEN_ERROR:
632                 case FRS_READ_ERROR:
633                         fclose(fh);
634                         break;
635
636                 case FRS_PRINT_ERROR:
637                         write_failure_alert_box(to_name, errno);
638                         fclose(fh);
639                         break;
640                 }
641         } else {
642                 stream = print_stream_text_stdio_new(fh);
643                 switch (follow_read_stream(follow_info, follow_print_text,
644                                            stream)) {
645                 case FRS_OK:
646                         if (!destroy_print_stream(stream))
647                                 write_failure_alert_box(to_name, errno);
648                         break;
649
650                 case FRS_OPEN_ERROR:
651                 case FRS_READ_ERROR:
652                         destroy_print_stream(stream);
653                         break;
654
655                 case FRS_PRINT_ERROR:
656                         write_failure_alert_box(to_name, errno);
657                         destroy_print_stream(stream);
658                         break;
659                 }
660         }
661
662         /* Save the directory name for future file dialogs. */
663         dirname = get_dirname(to_name);  /* Overwrites to_name */
664         set_last_open_dir(dirname);
665         g_free(to_name);
666         return TRUE;
667 }
668
669 static void
670 follow_save_as_destroy_cb(GtkWidget * win _U_, gpointer data)
671 {
672         follow_info_t   *follow_info = data;
673
674         /* Note that we no longer have a dialog box. */
675         follow_info->follow_save_as_w = NULL;
676 }
677
678 static void
679 follow_stream_direction_changed(GtkWidget *w, gpointer data)
680 {
681         follow_info_t *follow_info = data;
682
683         switch(gtk_combo_box_get_active(GTK_COMBO_BOX(w))) {
684
685         case 0 :
686                 follow_info->show_stream = BOTH_HOSTS;
687                 follow_load_text(follow_info);
688                 break;
689         case 1 :
690                 follow_info->show_stream = FROM_CLIENT;
691                 follow_load_text(follow_info);
692                 break;
693         case 2 :
694                 follow_info->show_stream = FROM_SERVER;
695                 follow_load_text(follow_info);
696                 break;
697         }
698 }
699
700 /* Add a "follow_info_t" structure to the list. */
701 static void
702 remember_follow_info(follow_info_t *follow_info)
703 {
704         follow_infos = g_list_append(follow_infos, follow_info);
705 }
706
707 #define IS_SHOW_TYPE(x) (follow_info->show_type == x ? 1 : 0)
708 /* Remove a "follow_info_t" structure from the list. */
709 static void
710 forget_follow_info(follow_info_t *follow_info)
711 {
712         follow_infos = g_list_remove(follow_infos, follow_info);
713 }
714
715 void
716 follow_stream(gchar *title, follow_info_t *follow_info,
717               gchar *both_directions_string,
718               gchar *server_to_client_string, gchar *client_to_server_string)
719 {
720         GtkWidget       *streamwindow, *vbox, *txt_scrollw, *text;
721         GtkWidget       *hbox, *bbox, *button, *radio_bt;
722         GtkWidget       *stream_fr, *stream_vb, *direction_hbox;
723         GtkWidget       *stream_cmb;
724         GtkTooltips     *tooltips;
725         follow_stats_t stats;
726
727         follow_info->show_type = SHOW_RAW;
728
729         streamwindow = dlg_window_new(title);
730
731         /* needed in follow_filter_out_stream(), is there a better way? */
732         follow_info->streamwindow = streamwindow;
733
734         gtk_widget_set_name(streamwindow, title);
735         gtk_window_set_default_size(GTK_WINDOW(streamwindow),
736                                     DEF_WIDTH, DEF_HEIGHT);
737         gtk_container_set_border_width(GTK_CONTAINER(streamwindow), 6);
738
739         /* setup the container */
740         tooltips = gtk_tooltips_new ();
741
742         vbox = gtk_vbox_new(FALSE, 6);
743         gtk_container_add(GTK_CONTAINER(streamwindow), vbox);
744
745         /* content frame */
746         if (incomplete_tcp_stream) {
747                 stream_fr = gtk_frame_new("Stream Content (incomplete)");
748         } else {
749                 stream_fr = gtk_frame_new("Stream Content");
750         }
751         gtk_container_add(GTK_CONTAINER(vbox), stream_fr);
752         gtk_widget_show(stream_fr);
753
754         stream_vb = gtk_vbox_new(FALSE, 6);
755         gtk_container_set_border_width( GTK_CONTAINER(stream_vb) , 6);
756         gtk_container_add(GTK_CONTAINER(stream_fr), stream_vb);
757
758         /* create a scrolled window for the text */
759         txt_scrollw = scrolled_window_new(NULL, NULL);
760         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(txt_scrollw),
761                                             GTK_SHADOW_IN);
762         gtk_box_pack_start(GTK_BOX(stream_vb), txt_scrollw, TRUE, TRUE, 0);
763
764         /* create a text box */
765         text = gtk_text_view_new();
766         gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
767         gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
768
769         gtk_container_add(GTK_CONTAINER(txt_scrollw), text);
770         follow_info->text = text;
771
772         /* direction hbox */
773         direction_hbox = gtk_hbox_new(FALSE, 1);
774         gtk_box_pack_start(GTK_BOX(stream_vb), direction_hbox, FALSE, FALSE, 0);
775
776         stream_cmb = gtk_combo_box_new_text();
777
778         gtk_combo_box_append_text(GTK_COMBO_BOX(stream_cmb),
779                                   both_directions_string);
780         follow_info->show_stream = BOTH_HOSTS;
781
782         gtk_combo_box_append_text(GTK_COMBO_BOX(stream_cmb),
783                                   server_to_client_string);
784
785         gtk_combo_box_append_text(GTK_COMBO_BOX(stream_cmb),
786                                    client_to_server_string);
787
788         gtk_combo_box_set_active(GTK_COMBO_BOX(stream_cmb), 0); /* Do this before signal_connect  */
789                                                                 /*  so callback not triggered     */
790
791         g_signal_connect(stream_cmb, "changed",
792                          G_CALLBACK(follow_stream_direction_changed),
793                          follow_info);
794
795         gtk_tooltips_set_tip (tooltips, stream_cmb,
796                               "Select the stream direction to display", NULL);
797         gtk_box_pack_start(GTK_BOX(direction_hbox), stream_cmb, TRUE, TRUE, 0);
798
799         /* stream hbox */
800         hbox = gtk_hbox_new(FALSE, 1);
801         gtk_box_pack_start(GTK_BOX(stream_vb), hbox, FALSE, FALSE, 0);
802
803         /* Create Find Button */
804         button = gtk_button_new_from_stock(GTK_STOCK_FIND);
805         g_signal_connect(button, "clicked", G_CALLBACK(follow_find_cb), follow_info);
806         gtk_tooltips_set_tip (tooltips, button, "Find text in the displayed content", NULL);
807         gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
808
809         /* Create Save As Button */
810         button = gtk_button_new_from_stock(GTK_STOCK_SAVE_AS);
811         g_signal_connect(button, "clicked", G_CALLBACK(follow_save_as_cmd_cb), follow_info);
812         gtk_tooltips_set_tip (tooltips, button, "Save the content as currently displayed", NULL);
813         gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
814
815         /* Create Print Button */
816         button = gtk_button_new_from_stock(GTK_STOCK_PRINT);
817         g_signal_connect(button, "clicked", G_CALLBACK(follow_print_stream), follow_info);
818         gtk_tooltips_set_tip(tooltips, button, "Print the content as currently displayed", NULL);
819         gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
820
821         /* Stream to show */
822         follow_stats(&stats);
823
824         follow_info->is_ipv6 = stats.is_ipv6;
825
826         /* ASCII radio button */
827         radio_bt = gtk_radio_button_new_with_label(NULL, "ASCII");
828         gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"ASCII\" format", NULL);
829         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt),
830                 IS_SHOW_TYPE(SHOW_ASCII));
831         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, TRUE, TRUE, 0);
832         g_signal_connect(radio_bt, "toggled", G_CALLBACK(follow_charset_toggle_cb),
833                        follow_info);
834         follow_info->ascii_bt = radio_bt;
835
836         /* EBCDIC radio button */
837         radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_get_group
838                                                    (GTK_RADIO_BUTTON(radio_bt)),
839                                                    "EBCDIC");
840         gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"EBCDIC\" format", NULL);
841         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt),
842                 IS_SHOW_TYPE(SHOW_EBCDIC));
843         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, TRUE, TRUE, 0);
844         g_signal_connect(radio_bt, "toggled", G_CALLBACK(follow_charset_toggle_cb),
845                        follow_info);
846         follow_info->ebcdic_bt = radio_bt;
847
848         /* HEX DUMP radio button */
849         radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_get_group
850                                                    (GTK_RADIO_BUTTON(radio_bt)),
851                                                    "Hex Dump");
852         gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"Hexdump\" format", NULL);
853         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt),
854                 IS_SHOW_TYPE(SHOW_HEXDUMP));
855         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, TRUE, TRUE, 0);
856         g_signal_connect(radio_bt, "toggled", G_CALLBACK(follow_charset_toggle_cb),
857                        follow_info);
858         follow_info->hexdump_bt = radio_bt;
859
860         /* C Array radio button */
861         radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_get_group
862                                                    (GTK_RADIO_BUTTON(radio_bt)),
863                                                    "C Arrays");
864         gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"C Array\" format", NULL);
865         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt),
866                 IS_SHOW_TYPE(SHOW_CARRAY));
867         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, TRUE, TRUE, 0);
868         g_signal_connect(radio_bt, "toggled", G_CALLBACK(follow_charset_toggle_cb),
869                        follow_info);
870         follow_info->carray_bt = radio_bt;
871
872         /* Raw radio button */
873         radio_bt = gtk_radio_button_new_with_label(gtk_radio_button_get_group
874                                                    (GTK_RADIO_BUTTON(radio_bt)),
875                                                    "Raw");
876         gtk_tooltips_set_tip (tooltips, radio_bt, "Stream data output in \"Raw\" (binary) format. As this contains non printable characters, the screen output will be in ASCII format", NULL);
877         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_bt),
878                 IS_SHOW_TYPE(SHOW_RAW));
879         gtk_box_pack_start(GTK_BOX(hbox), radio_bt, TRUE, TRUE, 0);
880         g_signal_connect(radio_bt, "toggled", G_CALLBACK(follow_charset_toggle_cb),
881                        follow_info);
882         follow_info->raw_bt = radio_bt;
883
884         /* Button row: help, filter out, close button */
885         bbox = dlg_button_row_new(WIRESHARK_STOCK_FILTER_OUT_STREAM,
886                                   GTK_STOCK_CLOSE, GTK_STOCK_HELP,
887                                   NULL);
888         gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 5);
889
890
891         button = g_object_get_data(G_OBJECT(bbox), WIRESHARK_STOCK_FILTER_OUT_STREAM);
892         gtk_tooltips_set_tip (tooltips, button,
893                               "Build a display filter which cuts this stream from the capture", NULL);
894         g_signal_connect(button, "clicked", G_CALLBACK(follow_filter_out_stream),
895                        follow_info);
896
897         button = g_object_get_data(G_OBJECT(bbox), GTK_STOCK_CLOSE);
898         window_set_cancel_button(streamwindow, button, window_cancel_button_cb);
899         gtk_tooltips_set_tip (tooltips, button,
900                               "Close the dialog and keep the current display filter", NULL);
901         gtk_widget_grab_default(button);
902
903         button = g_object_get_data(G_OBJECT(bbox), GTK_STOCK_HELP);
904         g_signal_connect(button, "clicked", G_CALLBACK(topic_cb),
905                          (gpointer)HELP_FOLLOW_STREAM_DIALOG);
906
907         /* Tuck away the follow_info object into the window */
908         g_object_set_data(G_OBJECT(streamwindow), E_FOLLOW_INFO_KEY, follow_info);
909
910         follow_load_text(follow_info);
911         remember_follow_info(follow_info);
912
913
914         g_signal_connect(streamwindow, "delete_event", G_CALLBACK(window_delete_event_cb), NULL);
915         g_signal_connect(streamwindow, "destroy", G_CALLBACK(follow_destroy_cb), NULL);
916
917         /* Make sure this widget gets destroyed if we quit the main loop,
918            so that if we exit, we clean up any temporary files we have
919            for "Follow TCP Stream" windows. */
920         gtk_quit_add_destroy(gtk_main_level(), GTK_OBJECT(streamwindow));
921
922         gtk_widget_show_all(streamwindow);
923         window_present(streamwindow);
924 }
925
926 /* The destroy call back has the responsibility of
927  * unlinking the temporary file
928  * and freeing the filter_out_filter */
929 static void
930 follow_destroy_cb(GtkWidget *w, gpointer data _U_)
931 {
932         follow_info_t *follow_info;
933         follow_record_t *follow_record;
934         GList *cur;
935         int i;
936
937         follow_info = g_object_get_data(G_OBJECT(w), E_FOLLOW_INFO_KEY);
938
939         switch(follow_info->follow_type) {
940
941         case FOLLOW_TCP :
942                 i = ws_unlink(follow_info->data_out_filename);
943                 if(i != 0) {
944                         g_warning("Follow: Couldn't remove temporary file: \"%s\", errno: %s (%u)", follow_info->data_out_filename, g_strerror(errno), errno);
945                 }
946                 break;
947
948         case FOLLOW_UDP :
949                 for(cur = follow_info->payload; cur; cur = g_list_next(cur))
950                         if(cur->data) {
951                                 follow_record = cur->data;
952                                 if(follow_record->data)
953                                         g_byte_array_free(follow_record->data,
954                                                           TRUE);
955
956                                 g_free(follow_record);
957                         }
958
959                 g_list_free(follow_info->payload);
960                 break;
961
962         case FOLLOW_SSL :
963                 /* free decrypted data list*/
964                 for (cur = follow_info->payload; cur; cur = g_list_next(cur))
965                         if (cur->data)
966                                 {
967                                         g_free(cur->data);
968                                         cur->data = NULL;
969                                 }
970                 g_list_free (follow_info->payload);
971                 break;
972         }
973
974         g_free(follow_info->data_out_filename);
975         g_free(follow_info->filter_out_filter);
976         g_free((gpointer)follow_info->client_ip.data);
977         forget_follow_info(follow_info);
978         g_free(follow_info);
979 }
980
981 frs_return_t
982 follow_show(follow_info_t *follow_info,
983             gboolean (*print_line_fcn_p)(char *, size_t, gboolean, void *),
984             char *buffer, size_t nchars, gboolean is_server, void *arg,
985             guint32 *global_pos, guint32 *server_packet_count,
986             guint32 *client_packet_count)
987 {
988         gchar initbuf[256];
989         guint32 current_pos;
990         static const gchar hexchars[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
991
992         switch (follow_info->show_type) {
993
994         case SHOW_EBCDIC:
995                 /* If our native arch is ASCII, call: */
996                 EBCDIC_to_ASCII(buffer, (guint) nchars);
997                 if (!(*print_line_fcn_p) (buffer, nchars, is_server, arg))
998                         return FRS_PRINT_ERROR;
999                 break;
1000
1001         case SHOW_ASCII:
1002                 /* If our native arch is EBCDIC, call:
1003                  * ASCII_TO_EBCDIC(buffer, nchars);
1004                  */
1005                 if (!(*print_line_fcn_p) (buffer, nchars, is_server, arg))
1006                         return FRS_PRINT_ERROR;
1007                 break;
1008
1009         case SHOW_RAW:
1010                 /* Don't translate, no matter what the native arch
1011                  * is.
1012                  */
1013                 if (!(*print_line_fcn_p) (buffer, nchars, is_server, arg))
1014                         return FRS_PRINT_ERROR;
1015                 break;
1016
1017         case SHOW_HEXDUMP:
1018                 current_pos = 0;
1019                 while (current_pos < nchars) {
1020                         gchar hexbuf[256];
1021                         int i;
1022                         gchar *cur = hexbuf, *ascii_start;
1023
1024                         /* is_server indentation : put 4 spaces at the
1025                          * beginning of the string */
1026                         /* XXX - We might want to prepend each line with "C" or "S" instead. */
1027                         if (is_server && follow_info->show_stream == BOTH_HOSTS) {
1028                                 memset(cur, ' ', 4);
1029                                 cur += 4;
1030                         }
1031                         cur += g_snprintf(cur, 20, "%08X  ", *global_pos);
1032                         /* 49 is space consumed by hex chars */
1033                         ascii_start = cur + 49;
1034                         for (i = 0; i < 16 && current_pos + i < nchars; i++) {
1035                                 *cur++ =
1036                                         hexchars[(buffer[current_pos + i] & 0xf0) >> 4];
1037                                 *cur++ =
1038                                         hexchars[buffer[current_pos + i] & 0x0f];
1039                                 *cur++ = ' ';
1040                                 if (i == 7)
1041                                         *cur++ = ' ';
1042                         }
1043                         /* Fill it up if column isn't complete */
1044                         while (cur < ascii_start)
1045                                 *cur++ = ' ';
1046
1047                         /* Now dump bytes as text */
1048                         for (i = 0; i < 16 && current_pos + i < nchars; i++) {
1049                                 *cur++ =
1050                                         (isprint((guchar)buffer[current_pos + i]) ?
1051                                          buffer[current_pos + i] : '.' );
1052                                 if (i == 7) {
1053                                         *cur++ = ' ';
1054                                 }
1055                         }
1056                         current_pos += i;
1057                         (*global_pos) += i;
1058                         *cur++ = '\n';
1059                         *cur = 0;
1060                         if (!(*print_line_fcn_p) (hexbuf, strlen(hexbuf), is_server, arg))
1061                                 return FRS_PRINT_ERROR;
1062                 }
1063                 break;
1064
1065         case SHOW_CARRAY:
1066                 current_pos = 0;
1067                 g_snprintf(initbuf, sizeof(initbuf), "char peer%d_%d[] = {\n",
1068                            is_server ? 1 : 0,
1069                            is_server ? (*server_packet_count)++ : (*client_packet_count)++);
1070                 if (!(*print_line_fcn_p) (initbuf, strlen(initbuf), is_server, arg))
1071                         return FRS_PRINT_ERROR;
1072
1073                 while (current_pos < nchars) {
1074                         gchar hexbuf[256];
1075                         int i, cur;
1076
1077                         cur = 0;
1078                         for (i = 0; i < 8 && current_pos + i < nchars; i++) {
1079                                 /* Prepend entries with "0x" */
1080                                 hexbuf[cur++] = '0';
1081                                 hexbuf[cur++] = 'x';
1082                                 hexbuf[cur++] =
1083                                         hexchars[(buffer[current_pos + i] & 0xf0) >> 4];
1084                                 hexbuf[cur++] =
1085                                         hexchars[buffer[current_pos + i] & 0x0f];
1086
1087                                 /* Delimit array entries with a comma */
1088                                 if (current_pos + i + 1 < nchars)
1089                                         hexbuf[cur++] = ',';
1090
1091                                 hexbuf[cur++] = ' ';
1092                         }
1093
1094                         /* Terminate the array if we are at the end */
1095                         if (current_pos + i == nchars) {
1096                                 hexbuf[cur++] = '}';
1097                                 hexbuf[cur++] = ';';
1098                         }
1099
1100                         current_pos += i;
1101                         (*global_pos) += i;
1102                         hexbuf[cur++] = '\n';
1103                         hexbuf[cur] = 0;
1104                         if (!(*print_line_fcn_p) (hexbuf, strlen(hexbuf), is_server, arg))
1105                                 return FRS_PRINT_ERROR;
1106                 }
1107                 break;
1108         }
1109
1110         return FRS_OK;
1111 }