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