From e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 22:34:10 +0200 Subject: Adding upstream version 4.2.2. Signed-off-by: Daniel Baumann --- file.c | 5443 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 5443 insertions(+) create mode 100644 file.c (limited to 'file.c') diff --git a/file.c b/file.c new file mode 100644 index 00000000..d8178ff7 --- /dev/null +++ b/file.c @@ -0,0 +1,5443 @@ +/* file.c + * File I/O routines + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#define WS_LOG_DOMAIN LOG_DOMAIN_CAPTURE + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cfile.h" +#include "file.h" +#include "fileset.h" +#include "frame_tvbuff.h" + +#include "ui/alert_box.h" +#include "ui/simple_dialog.h" +#include "ui/main_statusbar.h" +#include "ui/progress_dlg.h" +#include "ui/urls.h" +#include "ui/ws_ui_util.h" +#include "ui/packet_list_utils.h" + +/* Needed for addrinfo */ +#include + +#ifdef HAVE_SYS_SOCKET_H +#include +#endif + +#ifdef HAVE_NETINET_IN_H +# include +#endif + +#ifdef _WIN32 +# include +# include +#endif + +static gboolean read_record(capture_file *cf, wtap_rec *rec, Buffer *buf, + dfilter_t *dfcode, epan_dissect_t *edt, column_info *cinfo, gint64 offset, + fifo_string_cache_t *frame_dup_cache, GChecksum *frame_cksum); + +static void rescan_packets(capture_file *cf, const char *action, const char *action_item, gboolean redissect); + +typedef enum { + MR_NOTMATCHED, + MR_MATCHED, + MR_ERROR +} match_result; +typedef match_result (*ws_match_function)(capture_file *, frame_data *, + wtap_rec *, Buffer *, void *); +static match_result match_protocol_tree(capture_file *cf, frame_data *fdata, + wtap_rec *, Buffer *, void *criterion); +static void match_subtree_text(proto_node *node, gpointer data); +static match_result match_summary_line(capture_file *cf, frame_data *fdata, + wtap_rec *, Buffer *, void *criterion); +static match_result match_narrow_and_wide(capture_file *cf, frame_data *fdata, + wtap_rec *, Buffer *, void *criterion); +static match_result match_narrow_and_wide_case(capture_file *cf, frame_data *fdata, + wtap_rec *, Buffer *, void *criterion); +static match_result match_narrow(capture_file *cf, frame_data *fdata, + wtap_rec *, Buffer *, void *criterion); +static match_result match_narrow_case(capture_file *cf, frame_data *fdata, + wtap_rec *, Buffer *, void *criterion); +static match_result match_wide(capture_file *cf, frame_data *fdata, + wtap_rec *, Buffer *, void *criterion); +static match_result match_wide_case(capture_file *cf, frame_data *fdata, + wtap_rec *, Buffer *, void *criterion); +static match_result match_binary(capture_file *cf, frame_data *fdata, + wtap_rec *, Buffer *, void *criterion); +static match_result match_regex(capture_file *cf, frame_data *fdata, + wtap_rec *, Buffer *, void *criterion); +static match_result match_dfilter(capture_file *cf, frame_data *fdata, + wtap_rec *, Buffer *, void *criterion); +static match_result match_marked(capture_file *cf, frame_data *fdata, + wtap_rec *, Buffer *, void *criterion); +static match_result match_time_reference(capture_file *cf, frame_data *fdata, + wtap_rec *, Buffer *, void *criterion); +static gboolean find_packet(capture_file *cf, ws_match_function match_function, + void *criterion, search_direction dir); + +static void cf_rename_failure_alert_box(const char *filename, int err); + +/* Seconds spent processing packets between pushing UI updates. */ +#define PROGBAR_UPDATE_INTERVAL 0.150 + +/* Show the progress bar after this many seconds. */ +#define PROGBAR_SHOW_DELAY 0.5 + +/* + * Maximum number of records we support in a file. + * + * It is, at most, the maximum value of a guint32, as we use a guint32 + * for the frame number. + * + * We allow it to be set to a lower value; see issue #16908 for why + * we're doing this. Thanks, Qt! + */ +static guint32 max_records = G_MAXUINT32; + +void +cf_set_max_records(guint max_records_arg) +{ + max_records = max_records_arg; +} + +/* + * We could probably use g_signal_...() instead of the callbacks below but that + * would require linking our CLI programs to libgobject and creating an object + * instance for the signals. + */ +typedef struct { + cf_callback_t cb_fct; + gpointer user_data; +} cf_callback_data_t; + +static GList *cf_callbacks = NULL; + +static void +cf_callback_invoke(int event, gpointer data) +{ + cf_callback_data_t *cb; + GList *cb_item = cf_callbacks; + + /* there should be at least one interested */ + ws_assert(cb_item != NULL); + + while (cb_item != NULL) { + cb = (cf_callback_data_t *)cb_item->data; + cb->cb_fct(event, data, cb->user_data); + cb_item = g_list_next(cb_item); + } +} + +void +cf_callback_add(cf_callback_t func, gpointer user_data) +{ + cf_callback_data_t *cb; + + cb = g_new(cf_callback_data_t,1); + cb->cb_fct = func; + cb->user_data = user_data; + + cf_callbacks = g_list_prepend(cf_callbacks, cb); +} + +void +cf_callback_remove(cf_callback_t func, gpointer user_data) +{ + cf_callback_data_t *cb; + GList *cb_item = cf_callbacks; + + while (cb_item != NULL) { + cb = (cf_callback_data_t *)cb_item->data; + if (cb->cb_fct == func && cb->user_data == user_data) { + cf_callbacks = g_list_remove(cf_callbacks, cb); + g_free(cb); + return; + } + cb_item = g_list_next(cb_item); + } + + ws_assert_not_reached(); +} + +gulong +cf_get_computed_elapsed(capture_file *cf) +{ + return cf->computed_elapsed; +} + +static void +compute_elapsed(capture_file *cf, gint64 start_time) +{ + gint64 delta_time = g_get_monotonic_time() - start_time; + + cf->computed_elapsed = (gulong) (delta_time / 1000); /* ms */ +} + +static const nstime_t * +ws_get_frame_ts(struct packet_provider_data *prov, guint32 frame_num) +{ + const frame_data *fd = NULL; + if (prov->prev_dis && prov->prev_dis->num == frame_num) { + fd = prov->prev_dis; + } else if (prov->prev_cap && prov->prev_cap->num == frame_num) { + fd = prov->prev_cap; + } else if (prov->frames) { + fd = frame_data_sequence_find(prov->frames, frame_num); + } + + return (fd && fd->has_ts) ? &fd->abs_ts : NULL; +} + +static epan_t * +ws_epan_new(capture_file *cf) +{ + static const struct packet_provider_funcs funcs = { + ws_get_frame_ts, + cap_file_provider_get_interface_name, + cap_file_provider_get_interface_description, + cap_file_provider_get_modified_block + }; + + return epan_new(&cf->provider, &funcs); +} + +cf_status_t +cf_open(capture_file *cf, const char *fname, unsigned int type, gboolean is_tempfile, int *err) +{ + wtap *wth; + gchar *err_info; + + wth = wtap_open_offline(fname, type, err, &err_info, TRUE); + if (wth == NULL) + goto fail; + + /* The open succeeded. Close whatever capture file we had open, + and fill in the information for this file. */ + cf_close(cf); + + /* Initialize the record metadata. */ + wtap_rec_init(&cf->rec); + + /* XXX - we really want to initialize this after we've read all + the packets, so we know how much we'll ultimately need. */ + ws_buffer_init(&cf->buf, 1514); + + /* We're about to start reading the file. */ + cf->state = FILE_READ_IN_PROGRESS; + + cf->provider.wth = wth; + cf->f_datalen = 0; + + /* Set the file name because we need it to set the follow stream filter. + XXX - is that still true? We need it for other reasons, though, + in any case. */ + cf->filename = g_strdup(fname); + + /* Indicate whether it's a permanent or temporary file. */ + cf->is_tempfile = is_tempfile; + + /* No user changes yet. */ + cf->unsaved_changes = FALSE; + + cf->computed_elapsed = 0; + + cf->cd_t = wtap_file_type_subtype(cf->provider.wth); + cf->open_type = type; + cf->linktypes = g_array_sized_new(FALSE, FALSE, (guint) sizeof(int), 1); + cf->count = 0; + cf->packet_comment_count = 0; + cf->displayed_count = 0; + cf->marked_count = 0; + cf->ignored_count = 0; + cf->ref_time_count = 0; + cf->drops_known = FALSE; + cf->drops = 0; + cf->snap = wtap_snapshot_length(cf->provider.wth); + + /* Allocate a frame_data_sequence for the frames in this file */ + cf->provider.frames = new_frame_data_sequence(); + + nstime_set_zero(&cf->elapsed_time); + cf->provider.ref = NULL; + cf->provider.prev_dis = NULL; + cf->provider.prev_cap = NULL; + cf->cum_bytes = 0; + + /* Create new epan session for dissection. + * (The old one was freed in cf_close().) + */ + cf->epan = ws_epan_new(cf); + + packet_list_queue_draw(); + cf_callback_invoke(cf_cb_file_opened, cf); + + wtap_set_cb_new_ipv4(cf->provider.wth, add_ipv4_name); + wtap_set_cb_new_ipv6(cf->provider.wth, (wtap_new_ipv6_callback_t) add_ipv6_name); + wtap_set_cb_new_secrets(cf->provider.wth, secrets_wtap_callback); + + return CF_OK; + +fail: + cfile_open_failure_alert_box(fname, *err, err_info); + return CF_ERROR; +} + +/* + * Add an encapsulation type to cf->linktypes. + */ +static void +cf_add_encapsulation_type(capture_file *cf, int encap) +{ + guint i; + + for (i = 0; i < cf->linktypes->len; i++) { + if (g_array_index(cf->linktypes, gint, i) == encap) + return; /* it's already there */ + } + /* It's not already there - add it. */ + g_array_append_val(cf->linktypes, encap); +} + +/* Reset everything to a pristine state */ +void +cf_close(capture_file *cf) +{ + cf->stop_flag = FALSE; + if (cf->state == FILE_CLOSED || cf->state == FILE_READ_PENDING) + return; /* Nothing to do */ + + /* Die if we're in the middle of reading a file. */ + ws_assert(cf->state != FILE_READ_IN_PROGRESS); + ws_assert(!cf->read_lock); + + cf_callback_invoke(cf_cb_file_closing, cf); + + /* close things, if not already closed before */ + color_filters_cleanup(); + + if (cf->provider.wth) { + wtap_close(cf->provider.wth); + cf->provider.wth = NULL; + } + /* We have no file open... */ + if (cf->filename != NULL) { + /* If it's a temporary file, remove it. */ + if (cf->is_tempfile) + ws_unlink(cf->filename); + g_free(cf->filename); + cf->filename = NULL; + } + /* ...which means we have no changes to that file to save. */ + cf->unsaved_changes = FALSE; + + /* no open_routine type */ + cf->open_type = WTAP_TYPE_AUTO; + + /* Clean up the record metadata. */ + wtap_rec_cleanup(&cf->rec); + + /* Clear the packet list. */ + packet_list_freeze(); + packet_list_clear(); + packet_list_thaw(); + + /* Free up the packet buffer. */ + ws_buffer_free(&cf->buf); + + dfilter_free(cf->rfcode); + cf->rfcode = NULL; + if (cf->provider.frames != NULL) { + free_frame_data_sequence(cf->provider.frames); + cf->provider.frames = NULL; + } + if (cf->provider.frames_modified_blocks) { + g_tree_destroy(cf->provider.frames_modified_blocks); + cf->provider.frames_modified_blocks = NULL; + } + cf_unselect_packet(cf); /* nothing to select */ + cf->first_displayed = 0; + cf->last_displayed = 0; + + /* No frames, no frame selected, no field in that frame selected. */ + cf->count = 0; + cf->current_frame = NULL; + cf->finfo_selected = NULL; + + /* No frame link-layer types, either. */ + if (cf->linktypes != NULL) { + g_array_free(cf->linktypes, TRUE); + cf->linktypes = NULL; + } + + cf->f_datalen = 0; + nstime_set_zero(&cf->elapsed_time); + + reset_tap_listeners(); + + epan_free(cf->epan); + cf->epan = NULL; + + /* We have no file open. */ + cf->state = FILE_CLOSED; + + cf_callback_invoke(cf_cb_file_closed, cf); +} + +/* + * TRUE if the progress dialog doesn't exist and it looks like we'll + * take > PROGBAR_SHOW_DELAY (500ms) to load, FALSE otherwise. + */ +static inline gboolean +progress_is_slow(progdlg_t *progdlg, GTimer *prog_timer, gint64 size, gint64 pos) +{ + double elapsed; + + if (progdlg) return FALSE; + elapsed = g_timer_elapsed(prog_timer, NULL); + /* This only gets checked between reading records, which doesn't help if + * a single record takes a very long time, e.g., the first TLS packet if + * the SSLKEYLOGFILE is very large. (#17051) */ + if ((elapsed * 2 > PROGBAR_SHOW_DELAY && (size / pos) >= 2) /* It looks like we're going to be slow. */ + || elapsed > PROGBAR_SHOW_DELAY) { /* We are indeed slow. */ + return TRUE; + } + return FALSE; +} + +static float +calc_progbar_val(capture_file *cf, gint64 size, gint64 file_pos, gchar *status_str, gulong status_size) +{ + float progbar_val; + + progbar_val = (gfloat) file_pos / (gfloat) size; + if (progbar_val > 1.0) { + + /* The file probably grew while we were reading it. + * Update file size, and try again. + */ + size = wtap_file_size(cf->provider.wth, NULL); + + if (size >= 0) + progbar_val = (gfloat) file_pos / (gfloat) size; + + /* If it's still > 1, either "wtap_file_size()" failed (in which + * case there's not much we can do about it), or the file + * *shrank* (in which case there's not much we can do about + * it); just clip the progress value at 1.0. + */ + if (progbar_val > 1.0f) + progbar_val = 1.0f; + } + + snprintf(status_str, status_size, + "%" PRId64 "KB of %" PRId64 "KB", + file_pos / 1024, size / 1024); + + return progbar_val; +} + +cf_read_status_t +cf_read(capture_file *cf, gboolean reloading) +{ + int err = 0; + gchar *err_info = NULL; + volatile gboolean too_many_records = FALSE; + gchar *name_ptr; + progdlg_t *volatile progbar = NULL; + GTimer *prog_timer = g_timer_new(); + gint64 size; + gint64 start_time; + epan_dissect_t edt; + wtap_rec rec; + Buffer buf; + dfilter_t *dfcode = NULL; + column_info *cinfo; + volatile gboolean create_proto_tree; + guint tap_flags; + gboolean compiled _U_; + volatile gboolean is_read_aborted = FALSE; + + /* The update_progress_dlg call below might end up accepting a user request to + * trigger redissection/rescans which can modify/destroy the dissection + * context ("cf->epan"). That condition should be prevented by callers, but in + * case it occurs let's fail gracefully. + */ + if (cf->read_lock) { + ws_warning("Failing due to recursive cf_read(\"%s\", %d) call!", + cf->filename, reloading); + return CF_READ_ERROR; + } + cf->read_lock = TRUE; + + /* Compile the current display filter. + * We assume this will not fail since cf->dfilter is only set in + * cf_filter IFF the filter was valid. + */ + if (cf->dfilter) { + compiled = dfilter_compile(cf->dfilter, &dfcode, NULL); + ws_assert(compiled && dfcode); + } + + /* Get the union of the flags for all tap listeners. */ + tap_flags = union_of_tap_listener_flags(); + + /* + * Determine whether we need to create a protocol tree. + * We do if: + * + * we're going to apply a display filter; + * + * one of the tap listeners is going to apply a filter; + * + * one of the tap listeners requires a protocol tree; + * + * a postdissector wants field values or protocols on + * the first pass. + */ + create_proto_tree = + (dfcode != NULL || have_filtering_tap_listeners() || + (tap_flags & TL_REQUIRES_PROTO_TREE) || postdissectors_want_hfids()); + + reset_tap_listeners(); + + name_ptr = g_filename_display_basename(cf->filename); + + if (reloading) + cf_callback_invoke(cf_cb_file_reload_started, cf); + else + cf_callback_invoke(cf_cb_file_read_started, cf); + + /* Record the file's compression type. + XXX - do we know this at open time? */ + cf->compression_type = wtap_get_compression_type(cf->provider.wth); + + /* The packet list window will be empty until the file is completely loaded */ + packet_list_freeze(); + + cf->stop_flag = FALSE; + start_time = g_get_monotonic_time(); + + epan_dissect_init(&edt, cf->epan, create_proto_tree, FALSE); + + /* If the display filter or any tap listeners require the columns, + * construct them. */ + cinfo = (tap_listeners_require_columns() || + dfilter_requires_columns(dfcode)) ? &cf->cinfo : NULL; + + /* Find the size of the file. */ + size = wtap_file_size(cf->provider.wth, NULL); + + /* If we are to ignore duplicate frames, we need a container to store + * hashes frame contents */ + fifo_string_cache_t frame_dup_cache; + GChecksum *volatile cksum = NULL; + + if (prefs.ignore_dup_frames) { + fifo_string_cache_init(&frame_dup_cache, prefs.ignore_dup_frames_cache_entries, g_free); + cksum = g_checksum_new(G_CHECKSUM_SHA256); + } + + g_timer_start(prog_timer); + + wtap_rec_init(&rec); + ws_buffer_init(&buf, 1514); + + TRY { + gint64 file_pos; + gint64 data_offset; + + float progbar_val; + gchar status_str[100]; + + while ((wtap_read(cf->provider.wth, &rec, &buf, &err, &err_info, + &data_offset))) { + if (size >= 0) { + if (cf->count == max_records) { + /* + * Quit if we've already read the maximum number of + * records allowed. + */ + too_many_records = TRUE; + break; + } + file_pos = wtap_read_so_far(cf->provider.wth); + + /* Create the progress bar if necessary. */ + if (progress_is_slow(progbar, prog_timer, size, file_pos)) { + progbar_val = calc_progbar_val(cf, size, file_pos, status_str, sizeof(status_str)); + progbar = delayed_create_progress_dlg(cf->window, NULL, NULL, TRUE, + &cf->stop_flag, progbar_val); + } + + /* + * Update the progress bar, but do it only after + * PROGBAR_UPDATE_INTERVAL has elapsed. Calling update_progress_dlg + * and packets_bar_update will likely trigger UI paint events, which + * might take a while depending on the platform and display. Reset + * our timer *after* painting. + */ + if (progbar && g_timer_elapsed(prog_timer, NULL) > PROGBAR_UPDATE_INTERVAL) { + progbar_val = calc_progbar_val(cf, size, file_pos, status_str, sizeof(status_str)); + /* update the packet bar content on the first run or frequently on very large files */ + update_progress_dlg(progbar, progbar_val, status_str); + compute_elapsed(cf, start_time); + packets_bar_update(); + g_timer_start(prog_timer); + } + /* + * The previous GUI triggers should not have destroyed the running + * session. If that did happen, it could blow up when read_record tries + * to use the destroyed edt.session, so detect it right here. + */ + ws_assert(edt.session == cf->epan); + } + + if (cf->state == FILE_READ_ABORTED) { + /* Well, the user decided to exit Wireshark. Break out of the + loop, and let the code below (which is called even if there + aren't any packets left to read) exit. */ + is_read_aborted = TRUE; + break; + } + if (cf->stop_flag) { + /* Well, the user decided to abort the read. He/She will be warned and + it might be enough for him/her to work with the already loaded + packets. + This is especially true for very large capture files, where you don't + want to wait loading the whole file (which may last minutes or even + hours even on fast machines) just to see that it was the wrong file. */ + break; + } + read_record(cf, &rec, &buf, dfcode, &edt, cinfo, data_offset, &frame_dup_cache, cksum); + wtap_rec_reset(&rec); + } + } + CATCH(OutOfMemoryError) { + simple_message_box(ESD_TYPE_ERROR, NULL, + "More information and workarounds can be found at\n" + WS_WIKI_URL("KnownBugs/OutOfMemory"), + "Sorry, but Wireshark has run out of memory and has to terminate now."); +#if 0 + /* Could we close the current capture and free up memory from that? */ +#else + /* we have to terminate, as we cannot recover from the memory error */ + exit(1); +#endif + } + ENDTRY; + + // If we're ignoring duplicate frames, clear the data structures. + // We really could look at prefs.ignore_dup_frames here, but it's even + // safer to check if we had allocated 'cksum'. + if (cksum != NULL) { + fifo_string_cache_free(&frame_dup_cache); + g_checksum_free(cksum); + } + + /* We're done reading sequentially through the file. */ + cf->state = FILE_READ_DONE; + + /* Destroy the progress bar if it was created. */ + if (progbar != NULL) + destroy_progress_dlg(progbar); + g_timer_destroy(prog_timer); + + /* Free the display name */ + g_free(name_ptr); + + /* Cleanup and release all dfilter resources */ + dfilter_free(dfcode); + + epan_dissect_cleanup(&edt); + wtap_rec_cleanup(&rec); + ws_buffer_free(&buf); + + /* Close the sequential I/O side, to free up memory it requires. */ + wtap_sequential_close(cf->provider.wth); + + /* Allow the protocol dissectors to free up memory that they + * don't need after the sequential run-through of the packets. */ + postseq_cleanup_all_protocols(); + + /* compute the time it took to load the file */ + compute_elapsed(cf, start_time); + + /* Set the file encapsulation type now; we don't know what it is until + we've looked at all the packets, as we don't know until then whether + there's more than one type (and thus whether it's + WTAP_ENCAP_PER_PACKET). */ + cf->lnk_t = wtap_file_encap(cf->provider.wth); + + cf->current_frame = frame_data_sequence_find(cf->provider.frames, cf->first_displayed); + + packet_list_thaw(); + + /* It is safe again to execute redissections or sort. */ + ws_assert(cf->read_lock); + cf->read_lock = FALSE; + + if (reloading) + cf_callback_invoke(cf_cb_file_reload_finished, cf); + else + cf_callback_invoke(cf_cb_file_read_finished, cf); + + /* If we have any displayed packets to select, select the first of those + packets by making the first row the selected row. */ + if (cf->first_displayed != 0) { + packet_list_select_row_from_data(NULL); + } + + if (is_read_aborted) { + /* + * Well, the user decided to exit Wireshark while reading this *offline* + * capture file (Live captures are handled by something like + * cf_continue_tail). Clean up accordingly. + */ + cf_close(cf); + cf->redissection_queued = RESCAN_NONE; + return CF_READ_ABORTED; + } + + if (cf->redissection_queued != RESCAN_NONE) { + /* Redissection was queued up. Clear the request and perform it now. */ + gboolean redissect = cf->redissection_queued == RESCAN_REDISSECT; + rescan_packets(cf, NULL, NULL, redissect); + } + + if (cf->stop_flag) { + simple_message_box(ESD_TYPE_WARN, NULL, + "The remaining packets in the file were discarded.\n" + "\n" + "As a lot of packets from the original file will be missing,\n" + "remember to be careful when saving the current content to a file.\n", + "File loading was cancelled."); + return CF_READ_ERROR; + } + + if (err != 0) { + /* Put up a message box noting that the read failed somewhere along + the line. Don't throw out the stuff we managed to read, though, + if any. */ + cfile_read_failure_alert_box(NULL, err, err_info); + return CF_READ_ERROR; + } else if (too_many_records) { + simple_message_box(ESD_TYPE_WARN, NULL, + "The remaining packets in the file were discarded.\n" + "\n" + "As a lot of packets from the original file will be missing,\n" + "remember to be careful when saving the current content to a file.\n" + "\n" + "The command-line utility editcap can be used to split " + "the file into multiple smaller files", + "The file contains more records than the maximum " + "supported number of records, %u.", max_records); + return CF_READ_ERROR; + } else + return CF_READ_OK; +} + +#ifdef HAVE_LIBPCAP +cf_read_status_t +cf_continue_tail(capture_file *cf, volatile int to_read, wtap_rec *rec, + Buffer *buf, int *err, fifo_string_cache_t *frame_dup_cache, GChecksum *frame_cksum) +{ + gchar *err_info; + volatile int newly_displayed_packets = 0; + dfilter_t *dfcode = NULL; + epan_dissect_t edt; + gboolean create_proto_tree; + guint tap_flags; + gboolean compiled _U_; + + /* Compile the current display filter. + * We assume this will not fail since cf->dfilter is only set in + * cf_filter IFF the filter was valid. + */ + if (cf->dfilter) { + compiled = dfilter_compile(cf->dfilter, &dfcode, NULL); + ws_assert(compiled && dfcode); + } + + /* Get the union of the flags for all tap listeners. */ + tap_flags = union_of_tap_listener_flags(); + + /* + * Determine whether we need to create a protocol tree. + * We do if: + * + * we're going to apply a display filter; + * + * one of the tap listeners is going to apply a filter; + * + * one of the tap listeners requires a protocol tree; + * + * a postdissector wants field values or protocols on + * the first pass. + */ + create_proto_tree = + (dfcode != NULL || have_filtering_tap_listeners() || + (tap_flags & TL_REQUIRES_PROTO_TREE) || postdissectors_want_hfids()); + + *err = 0; + + /* Don't freeze/thaw the list when doing live capture */ + /*packet_list_freeze();*/ + + epan_dissect_init(&edt, cf->epan, create_proto_tree, FALSE); + + TRY { + gint64 data_offset = 0; + column_info *cinfo; + + /* If the display filter or any tap listeners require the columns, + * construct them. */ + cinfo = (tap_listeners_require_columns() || + dfilter_requires_columns(dfcode)) ? &cf->cinfo : NULL; + + while (to_read != 0) { + wtap_cleareof(cf->provider.wth); + if (!wtap_read(cf->provider.wth, rec, buf, err, &err_info, + &data_offset)) { + break; + } + if (cf->state == FILE_READ_ABORTED) { + /* Well, the user decided to exit Wireshark. Break out of the + loop, and let the code below (which is called even if there + aren't any packets left to read) exit. */ + break; + } + if (read_record(cf, rec, buf, dfcode, &edt, cinfo, data_offset, frame_dup_cache, frame_cksum)) { + newly_displayed_packets++; + } + to_read--; + } + wtap_rec_reset(rec); + } + CATCH(OutOfMemoryError) { + simple_message_box(ESD_TYPE_ERROR, NULL, + "More information and workarounds can be found at\n" + WS_WIKI_URL("KnownBugs/OutOfMemory"), + "Sorry, but Wireshark has run out of memory and has to terminate now."); +#if 0 + /* Could we close the current capture and free up memory from that? */ + return CF_READ_ABORTED; +#else + /* we have to terminate, as we cannot recover from the memory error */ + exit(1); +#endif + } + ENDTRY; + + /* Update the file encapsulation; it might have changed based on the + packets we've read. */ + cf->lnk_t = wtap_file_encap(cf->provider.wth); + + /* Cleanup and release all dfilter resources */ + dfilter_free(dfcode); + + epan_dissect_cleanup(&edt); + + /* Don't freeze/thaw the list when doing live capture */ + /*packet_list_thaw();*/ + /* With the new packet list the first packet + * isn't automatically selected. + */ + if (!cf->current_frame && !packet_list_multi_select_active()) + packet_list_select_row_from_data(NULL); + + if (cf->state == FILE_READ_ABORTED) { + /* Well, the user decided to exit Wireshark. Return CF_READ_ABORTED + so that our caller can kill off the capture child process; + this will cause an EOF on the pipe from the child, so + "cf_finish_tail()" will be called, and it will clean up + and exit. */ + return CF_READ_ABORTED; + } else if (*err != 0) { + /* We got an error reading the capture file. + XXX - pop up a dialog box instead? */ + if (err_info != NULL) { + ws_warning("Error \"%s\" while reading \"%s\" (\"%s\")", + wtap_strerror(*err), cf->filename, err_info); + g_free(err_info); + } else { + ws_warning("Error \"%s\" while reading \"%s\"", + wtap_strerror(*err), cf->filename); + } + return CF_READ_ERROR; + } else + return CF_READ_OK; +} + +void +cf_fake_continue_tail(capture_file *cf) +{ + if (cf->state == FILE_CLOSED) { + cf->state = FILE_READ_PENDING; + } +} + +cf_read_status_t +cf_finish_tail(capture_file *cf, wtap_rec *rec, Buffer *buf, int *err, + fifo_string_cache_t *frame_dup_cache, GChecksum *frame_cksum) +{ + gchar *err_info; + gint64 data_offset; + dfilter_t *dfcode = NULL; + column_info *cinfo; + epan_dissect_t edt; + gboolean create_proto_tree; + guint tap_flags; + gboolean compiled _U_; + + /* Compile the current display filter. + * We assume this will not fail since cf->dfilter is only set in + * cf_filter IFF the filter was valid. + */ + if (cf->dfilter) { + compiled = dfilter_compile(cf->dfilter, &dfcode, NULL); + ws_assert(compiled && dfcode); + } + + /* Get the union of the flags for all tap listeners. */ + tap_flags = union_of_tap_listener_flags(); + + /* If the display filter or any tap listeners require the columns, + * construct them. */ + cinfo = (tap_listeners_require_columns() || + dfilter_requires_columns(dfcode)) ? &cf->cinfo : NULL; + + /* + * Determine whether we need to create a protocol tree. + * We do if: + * + * we're going to apply a display filter; + * + * one of the tap listeners is going to apply a filter; + * + * one of the tap listeners requires a protocol tree; + * + * a postdissector wants field values or protocols on + * the first pass. + */ + create_proto_tree = + (dfcode != NULL || have_filtering_tap_listeners() || + (tap_flags & TL_REQUIRES_PROTO_TREE) || postdissectors_want_hfids()); + + if (cf->provider.wth == NULL) { + cf_close(cf); + return CF_READ_ERROR; + } + + /* Don't freeze/thaw the list when doing live capture */ + /*packet_list_freeze();*/ + + epan_dissect_init(&edt, cf->epan, create_proto_tree, FALSE); + + while ((wtap_read(cf->provider.wth, rec, buf, err, &err_info, &data_offset))) { + if (cf->state == FILE_READ_ABORTED) { + /* Well, the user decided to abort the read. Break out of the + loop, and let the code below (which is called even if there + aren't any packets left to read) exit. */ + break; + } + read_record(cf, rec, buf, dfcode, &edt, cinfo, data_offset, frame_dup_cache, frame_cksum); + wtap_rec_reset(rec); + } + + /* Cleanup and release all dfilter resources */ + dfilter_free(dfcode); + + epan_dissect_cleanup(&edt); + + /* Don't freeze/thaw the list when doing live capture */ + /*packet_list_thaw();*/ + + if (cf->state == FILE_READ_ABORTED) { + /* Well, the user decided to abort the read. We're only called + when the child capture process closes the pipe to us (meaning + it's probably exited), so we can just close the capture + file; we return CF_READ_ABORTED so our caller can do whatever + is appropriate when that happens. */ + cf_close(cf); + return CF_READ_ABORTED; + } + + /* We're done reading sequentially through the file. */ + cf->state = FILE_READ_DONE; + + /* We're done reading sequentially through the file; close the + sequential I/O side, to free up memory it requires. */ + wtap_sequential_close(cf->provider.wth); + + /* Allow the protocol dissectors to free up memory that they + * don't need after the sequential run-through of the packets. */ + postseq_cleanup_all_protocols(); + + /* Update the file encapsulation; it might have changed based on the + packets we've read. */ + cf->lnk_t = wtap_file_encap(cf->provider.wth); + + /* Update the details in the file-set dialog, as the capture file + * has likely grown since we first stat-ed it */ + fileset_update_file(cf->filename); + + if (*err != 0) { + /* We got an error reading the capture file. + XXX - pop up a dialog box? */ + if (err_info != NULL) { + ws_warning("Error \"%s\" while reading \"%s\" (\"%s\")", + wtap_strerror(*err), cf->filename, err_info); + g_free(err_info); + } else { + ws_warning("Error \"%s\" while reading \"%s\"", + wtap_strerror(*err), cf->filename); + } + return CF_READ_ERROR; + } else { + return CF_READ_OK; + } +} +#endif /* HAVE_LIBPCAP */ + +gchar * +cf_get_display_name(capture_file *cf) +{ + gchar *displayname; + + /* Return a name to use in displays */ + if (!cf->is_tempfile) { + /* Get the last component of the file name, and use that. */ + if (cf->filename) { + displayname = g_filename_display_basename(cf->filename); + } else { + displayname=g_strdup("(No file)"); + } + } else { + /* The file we read is a temporary file from a live capture or + a merge operation; we don't mention its name, but, if it's + from a capture, give the source of the capture. */ + if (cf->source) { + displayname = g_strdup(cf->source); + } else { + displayname = g_strdup("(Untitled)"); + } + } + return displayname; +} + +gchar * +cf_get_basename(capture_file *cf) +{ + gchar *displayname; + + /* Return a name to use in the GUI for the basename for files to + which we save statistics */ + if (!cf->is_tempfile) { + /* Get the last component of the file name, and use that. */ + if (cf->filename) { + displayname = g_filename_display_basename(cf->filename); + + /* If the file name ends with any extension that corresponds + to a file type we support - including compressed versions + of those files - strip it off. */ + size_t displayname_len = strlen(displayname); + GSList *extensions = wtap_get_all_file_extensions_list(); + GSList *suffix; + for (suffix = extensions; suffix != NULL; suffix = g_slist_next(suffix)) { + /* Does the file name end with that extension? */ + const char *extension = (char *)suffix->data; + size_t extension_len = strlen(extension); + if (displayname_len > extension_len && + displayname[displayname_len - extension_len - 1] == '.' && + strcmp(&displayname[displayname_len - extension_len], extension) == 0) { + /* Yes. Strip the extension off, and return the result. */ + displayname[displayname_len - extension_len - 1] = '\0'; + break; + } + } + wtap_free_extensions_list(extensions); + } else { + displayname=g_strdup(""); + } + } else { + /* The file we read is a temporary file from a live capture or + a merge operation; we don't mention its name, but, if it's + from a capture, give the source of the capture. */ + if (cf->source) { + displayname = g_strdup(cf->source); + } else { + displayname = g_strdup(""); + } + } + return displayname; +} + +void +cf_set_tempfile_source(capture_file *cf, gchar *source) +{ + if (cf->source) { + g_free(cf->source); + } + + if (source) { + cf->source = g_strdup(source); + } else { + cf->source = g_strdup(""); + } +} + +const gchar * +cf_get_tempfile_source(capture_file *cf) +{ + if (!cf->source) { + return ""; + } + + return cf->source; +} + +/* XXX - use a macro instead? */ +int +cf_get_packet_count(capture_file *cf) +{ + return cf->count; +} + +/* XXX - use a macro instead? */ +gboolean +cf_is_tempfile(capture_file *cf) +{ + return cf->is_tempfile; +} + +void +cf_set_tempfile(capture_file *cf, gboolean is_tempfile) +{ + cf->is_tempfile = is_tempfile; +} + + +/* XXX - use a macro instead? */ +void +cf_set_drops_known(capture_file *cf, gboolean drops_known) +{ + cf->drops_known = drops_known; +} + +/* XXX - use a macro instead? */ +void +cf_set_drops(capture_file *cf, guint32 drops) +{ + cf->drops = drops; +} + +/* XXX - use a macro instead? */ +gboolean +cf_get_drops_known(capture_file *cf) +{ + return cf->drops_known; +} + +/* XXX - use a macro instead? */ +guint32 +cf_get_drops(capture_file *cf) +{ + return cf->drops; +} + +void +cf_set_rfcode(capture_file *cf, dfilter_t *rfcode) +{ + cf->rfcode = rfcode; +} + +static void +add_packet_to_packet_list(frame_data *fdata, capture_file *cf, + epan_dissect_t *edt, dfilter_t *dfcode, column_info *cinfo, + wtap_rec *rec, Buffer *buf, gboolean add_to_packet_list) +{ + frame_data_set_before_dissect(fdata, &cf->elapsed_time, + &cf->provider.ref, cf->provider.prev_dis); + cf->provider.prev_cap = fdata; + + if (dfcode != NULL) { + epan_dissect_prime_with_dfilter(edt, dfcode); + } +#if 0 + /* Prepare coloring rules, this ensures that display filter rules containing + * frame.color_rule references are still processed. + * TODO: actually detect that situation or maybe apply other optimizations? */ + if (edt->tree && color_filters_used()) { + color_filters_prime_edt(edt); + fdata->need_colorize = 1; + } +#endif + + if (!fdata->visited) { + /* This is the first pass, so prime the epan_dissect_t with the + hfids postdissectors want on the first pass. */ + prime_epan_dissect_with_postdissector_wanted_hfids(edt); + } + + /* Dissect the frame. */ + epan_dissect_run_with_taps(edt, cf->cd_t, rec, + frame_tvbuff_new_buffer(&cf->provider, fdata, buf), + fdata, cinfo); + + /* If we don't have a display filter, set "passed_dfilter" to 1. */ + if (dfcode != NULL) { + fdata->passed_dfilter = dfilter_apply_edt(dfcode, edt) ? 1 : 0; + + if (fdata->passed_dfilter && edt->pi.fd->dependent_frames) { + /* This frame passed the display filter but it may depend on other + * (potentially not displayed) frames. Find those frames and mark them + * as depended upon. + */ + g_hash_table_foreach(edt->pi.fd->dependent_frames, find_and_mark_frame_depended_upon, cf->provider.frames); + } + } else + fdata->passed_dfilter = 1; + + if (fdata->passed_dfilter || fdata->ref_time) + cf->displayed_count++; + + if (add_to_packet_list) { + /* We fill the needed columns from new_packet_list */ + packet_list_append(cinfo, fdata); + } + + if (fdata->passed_dfilter || fdata->ref_time) + { + frame_data_set_after_dissect(fdata, &cf->cum_bytes); + /* The only way we use prev_dis is to get the time stamp of + * the previous displayed frame, so ignore it if it doesn't + * have a time stamp, because we're presumably interested in + * the timestamp of the previously displayed frame with a + * time. XXX: What if in the future we want to use the previously + * displayed frame for something else, too? + */ + if (fdata->has_ts) { + cf->provider.prev_dis = fdata; + } + + /* If we haven't yet seen the first frame, this is it. */ + if (cf->first_displayed == 0) + cf->first_displayed = fdata->num; + + /* This is the last frame we've seen so far. */ + cf->last_displayed = fdata->num; + } + + epan_dissect_reset(edt); +} + +/* + * Read in a new record. + * Returns TRUE if the packet was added to the packet (record) list, + * FALSE otherwise. + */ +static gboolean +read_record(capture_file *cf, wtap_rec *rec, Buffer *buf, dfilter_t *dfcode, + epan_dissect_t *edt, column_info *cinfo, gint64 offset, + fifo_string_cache_t *frame_dup_cache, GChecksum *frame_cksum) +{ + frame_data fdlocal; + frame_data *fdata; + gboolean passed = TRUE; + gboolean added = FALSE; + const gchar *cksum_string; + gboolean was_in_cache; + + /* Add this packet's link-layer encapsulation type to cf->linktypes, if + it's not already there. + XXX - yes, this is O(N), so if every packet had a different + link-layer encapsulation type, it'd be O(N^2) to read the file, but + there are probably going to be a small number of encapsulation types + in a file. */ + if (rec->rec_type == REC_TYPE_PACKET) { + cf_add_encapsulation_type(cf, rec->rec_header.packet_header.pkt_encap); + } + + /* The frame number of this packet, if we add it to the set of frames, + would be one more than the count of frames in the file so far. */ + frame_data_init(&fdlocal, cf->count + 1, rec, offset, cf->cum_bytes); + + if (cf->rfcode) { + epan_dissect_t rf_edt; + column_info *rf_cinfo = NULL; + + epan_dissect_init(&rf_edt, cf->epan, TRUE, FALSE); + epan_dissect_prime_with_dfilter(&rf_edt, cf->rfcode); + if (dfilter_requires_columns(cf->rfcode)) { + rf_cinfo = &cf->cinfo; + } + epan_dissect_run(&rf_edt, cf->cd_t, rec, + frame_tvbuff_new_buffer(&cf->provider, &fdlocal, buf), + &fdlocal, rf_cinfo); + passed = dfilter_apply_edt(cf->rfcode, &rf_edt); + epan_dissect_cleanup(&rf_edt); + } + + if (passed) { + added = TRUE; + + /* This does a shallow copy of fdlocal, which is good enough. */ + fdata = frame_data_sequence_add(cf->provider.frames, &fdlocal); + + cf->count++; + if (rec->block != NULL) + cf->packet_comment_count += wtap_block_count_option(rec->block, OPT_COMMENT); + cf->f_datalen = offset + fdlocal.cap_len; + + // Should we check if the frame data is a duplicate, and thus, ignore + // this frame? + if (frame_cksum != NULL && rec->rec_type == REC_TYPE_PACKET) { + g_checksum_reset(frame_cksum); + g_checksum_update(frame_cksum, ws_buffer_start_ptr(buf), ws_buffer_length(buf)); + cksum_string = g_strdup(g_checksum_get_string(frame_cksum)); + was_in_cache = fifo_string_cache_insert(frame_dup_cache, cksum_string); + if (was_in_cache) { + g_free((gpointer)cksum_string); + fdata->ignored = TRUE; + cf->ignored_count++; + } + } + + /* When a redissection is in progress (or queued), do not process packets. + * This will be done once all (new) packets have been scanned. */ + if (!cf->redissecting && cf->redissection_queued == RESCAN_NONE) { + add_packet_to_packet_list(fdata, cf, edt, dfcode, cinfo, rec, buf, TRUE); + } + } + + return added; +} + + +typedef struct _callback_data_t { + gpointer pd_window; + gint64 f_len; + progdlg_t *progbar; + GTimer *prog_timer; + gboolean stop_flag; +} callback_data_t; + + +static gboolean +merge_callback(merge_event event, int num _U_, + const merge_in_file_t in_files[], const guint in_file_count, + void *data) +{ + guint i; + callback_data_t *cb_data = (callback_data_t*) data; + + ws_assert(cb_data != NULL); + + switch (event) { + + case MERGE_EVENT_INPUT_FILES_OPENED: + /* do nothing */ + break; + + case MERGE_EVENT_FRAME_TYPE_SELECTED: + /* do nothing */ + break; + + case MERGE_EVENT_READY_TO_MERGE: + /* Get the sum of the sizes of all the files. */ + for (i = 0; i < in_file_count; i++) + cb_data->f_len += in_files[i].size; + + cb_data->prog_timer = g_timer_new(); + g_timer_start(cb_data->prog_timer); + break; + + case MERGE_EVENT_RECORD_WAS_READ: + { + /* Create the progress bar if necessary. + We check on every iteration of the loop, so that it takes no + longer than the standard time to create it (otherwise, for a + large file, we might take considerably longer than that standard + time in order to get to the next progress bar step). */ + if (cb_data->progbar == NULL) { + cb_data->progbar = delayed_create_progress_dlg(cb_data->pd_window, NULL, NULL, + FALSE, &cb_data->stop_flag, 0.0f); + } + + /* + * Update the progress bar, but do it only after + * PROGBAR_UPDATE_INTERVAL has elapsed. Calling update_progress_dlg + * and packets_bar_update will likely trigger UI paint events, which + * might take a while depending on the platform and display. Reset + * our timer *after* painting. + */ + if (g_timer_elapsed(cb_data->prog_timer, NULL) > PROGBAR_UPDATE_INTERVAL) { + float progbar_val; + gint64 file_pos = 0; + /* Get the sum of the seek positions in all of the files. */ + for (i = 0; i < in_file_count; i++) + file_pos += wtap_read_so_far(in_files[i].wth); + + progbar_val = (gfloat) file_pos / (gfloat) cb_data->f_len; + if (progbar_val > 1.0f) { + /* Some file probably grew while we were reading it. + That "shouldn't happen", so we'll just clip the progress + value at 1.0. */ + progbar_val = 1.0f; + } + + if (cb_data->progbar != NULL) { + gchar status_str[100]; + snprintf(status_str, sizeof(status_str), + "%" PRId64 "KB of %" PRId64 "KB", + file_pos / 1024, cb_data->f_len / 1024); + update_progress_dlg(cb_data->progbar, progbar_val, status_str); + } + g_timer_start(cb_data->prog_timer); + } + } + break; + + case MERGE_EVENT_DONE: + /* We're done merging the files; destroy the progress bar if it was created. */ + if (cb_data->progbar != NULL) + destroy_progress_dlg(cb_data->progbar); + g_timer_destroy(cb_data->prog_timer); + break; + } + + return cb_data->stop_flag; +} + + + +cf_status_t +cf_merge_files_to_tempfile(gpointer pd_window, const char *temp_dir, char **out_filenamep, + int in_file_count, const char *const *in_filenames, + int file_type, gboolean do_append) +{ + int err = 0; + gchar *err_info = NULL; + guint err_fileno; + guint32 err_framenum; + merge_result status; + merge_progress_callback_t cb; + callback_data_t *cb_data = g_new0(callback_data_t, 1); + + /* prepare our callback routine */ + cb_data->pd_window = pd_window; + cb.callback_func = merge_callback; + cb.data = cb_data; + + cf_callback_invoke(cf_cb_file_merge_started, NULL); + + /* merge the files */ + status = merge_files_to_tempfile(temp_dir, out_filenamep, "wireshark", file_type, + in_filenames, + in_file_count, do_append, + IDB_MERGE_MODE_ALL_SAME, 0 /* snaplen */, + "Wireshark", &cb, &err, &err_info, + &err_fileno, &err_framenum); + + g_free(cb.data); + + switch (status) { + case MERGE_OK: + break; + + case MERGE_USER_ABORTED: + /* this isn't really an error, though we will return CF_ERROR later */ + break; + + case MERGE_ERR_CANT_OPEN_INFILE: + cfile_open_failure_alert_box(in_filenames[err_fileno], err, err_info); + break; + + case MERGE_ERR_CANT_OPEN_OUTFILE: + cfile_dump_open_failure_alert_box(*out_filenamep, err, err_info, + file_type); + break; + + case MERGE_ERR_CANT_READ_INFILE: + cfile_read_failure_alert_box(in_filenames[err_fileno], err, err_info); + break; + + case MERGE_ERR_BAD_PHDR_INTERFACE_ID: + simple_error_message_box("Record %u of \"%s\" has an interface ID that does not match any IDB in its file.", + err_framenum, in_filenames[err_fileno]); + break; + + case MERGE_ERR_CANT_WRITE_OUTFILE: + cfile_write_failure_alert_box(in_filenames[err_fileno], + *out_filenamep, err, err_info, + err_framenum, file_type); + break; + + case MERGE_ERR_CANT_CLOSE_OUTFILE: + cfile_close_failure_alert_box(*out_filenamep, err, err_info); + break; + + default: + simple_error_message_box("Unknown merge_files error %d", status); + break; + } + + cf_callback_invoke(cf_cb_file_merge_finished, NULL); + + if (status != MERGE_OK) { + /* Callers aren't expected to treat an error or an explicit abort + differently - we put up error dialogs ourselves, so they don't + have to. */ + return CF_ERROR; + } else + return CF_OK; +} + +cf_status_t +cf_filter_packets(capture_file *cf, gchar *dftext, gboolean force) +{ + const char *filter_new = dftext ? dftext : ""; + const char *filter_old = cf->dfilter ? cf->dfilter : ""; + dfilter_t *dfcode; + df_error_t *df_err; + + /* if new filter equals old one, do nothing unless told to do so */ + if (!force && strcmp(filter_new, filter_old) == 0) { + return CF_OK; + } + + dfcode=NULL; + + if (dftext == NULL) { + /* The new filter is an empty filter (i.e., display all packets). + * so leave dfcode==NULL + */ + } else { + /* + * We have a filter; make a copy of it (as we'll be saving it), + * and try to compile it. + */ + dftext = g_strdup(dftext); + if (!dfilter_compile(dftext, &dfcode, &df_err)) { + /* The attempt failed; report an error. */ + simple_message_box(ESD_TYPE_ERROR, NULL, + "See the help for a description of the display filter syntax.", + "\"%s\" isn't a valid display filter: %s", + dftext, df_err->msg); + df_error_free(&df_err); + g_free(dftext); + return CF_ERROR; + } + + /* Was it empty? */ + if (dfcode == NULL) { + /* Yes - free the filter text, and set it to null. */ + g_free(dftext); + dftext = NULL; + } + } + + /* We have a valid filter. Replace the current filter. */ + g_free(cf->dfilter); + cf->dfilter = dftext; + + + /* Now rescan the packet list, applying the new filter, but not + * throwing away information constructed on a previous pass. + * If a dissection is already in progress, queue it. + */ + if (cf->redissection_queued == RESCAN_NONE) { + if (cf->read_lock) { + cf->redissection_queued = RESCAN_SCAN; + } else if (cf->state != FILE_CLOSED) { + if (dftext == NULL) { + rescan_packets(cf, "Resetting", "filter", FALSE); + } else { + rescan_packets(cf, "Filtering", dftext, FALSE); + } + } + } + + /* Cleanup and release all dfilter resources */ + dfilter_free(dfcode); + + return CF_OK; +} + +void +cf_redissect_packets(capture_file *cf) +{ + if (cf->read_lock || cf->redissection_queued == RESCAN_SCAN) { + /* Dissection in progress, signal redissection rather than rescanning. That + * would destroy the current (in-progress) dissection in "cf_read" which + * will cause issues when "cf_read" tries to add packets to the list. + * If a previous rescan was requested, "upgrade" it to a full redissection. + */ + cf->redissection_queued = RESCAN_REDISSECT; + } + if (cf->redissection_queued != RESCAN_NONE) { + /* Redissection is (already) queued, wait for "cf_read" to finish. */ + return; + } + + if (cf->state != FILE_CLOSED) { + /* Restart dissection in case no cf_read is pending. */ + rescan_packets(cf, "Reprocessing", "all packets", TRUE); + } +} + +gboolean +cf_read_record(capture_file *cf, const frame_data *fdata, + wtap_rec *rec, Buffer *buf) +{ + int err; + gchar *err_info; + + if (!wtap_seek_read(cf->provider.wth, fdata->file_off, rec, buf, &err, &err_info)) { + cfile_read_failure_alert_box(cf->filename, err, err_info); + return FALSE; + } + return TRUE; +} + +gboolean +cf_read_record_no_alert(capture_file *cf, const frame_data *fdata, + wtap_rec *rec, Buffer *buf) +{ + int err; + gchar *err_info; + + if (!wtap_seek_read(cf->provider.wth, fdata->file_off, rec, buf, &err, &err_info)) { + g_free(err_info); + return FALSE; + } + return TRUE; +} + +gboolean +cf_read_current_record(capture_file *cf) +{ + return cf_read_record(cf, cf->current_frame, &cf->rec, &cf->buf); +} + +/* Rescan the list of packets, reconstructing the CList. + + "action" describes why we're doing this; it's used in the progress + dialog box. + + "action_item" describes what we're doing; it's used in the progress + dialog box. + + "redissect" is TRUE if we need to make the dissectors reconstruct + any state information they have (because a preference that affects + some dissector has changed, meaning some dissector might construct + its state differently from the way it was constructed the last time). */ +static void +rescan_packets(capture_file *cf, const char *action, const char *action_item, gboolean redissect) +{ + /* Rescan packets new packet list */ + guint32 framenum; + frame_data *fdata; + wtap_rec rec; + Buffer buf; + progdlg_t *progbar = NULL; + GTimer *prog_timer = g_timer_new(); + int count; + frame_data *selected_frame, *preceding_frame, *following_frame, *prev_frame; + int selected_frame_num, preceding_frame_num, following_frame_num, prev_frame_num; + gboolean selected_frame_seen; + float progbar_val; + gint64 start_time; + gchar status_str[100]; + epan_dissect_t edt; + dfilter_t *dfcode = NULL; + column_info *cinfo; + gboolean create_proto_tree; + gboolean filtering_tap_listeners = FALSE; + guint tap_flags; + gboolean add_to_packet_list = FALSE; + gboolean compiled _U_; + guint32 frames_count; + gboolean queued_rescan_type = RESCAN_NONE; + + if (cf->state == FILE_CLOSED || cf->state == FILE_READ_PENDING) { + return; + } + + /* Rescan in progress, clear pending actions. */ + cf->redissection_queued = RESCAN_NONE; + ws_assert(!cf->read_lock); + cf->read_lock = TRUE; + + wtap_rec_init(&rec); + ws_buffer_init(&buf, 1514); + + /* Compile the current display filter. + * We assume this will not fail since cf->dfilter is only set in + * cf_filter IFF the filter was valid. + */ + if (cf->dfilter) { + compiled = dfilter_compile(cf->dfilter, &dfcode, NULL); + ws_assert(compiled && dfcode); + } + + /* Do we have any tap listeners with filters? */ + filtering_tap_listeners = have_filtering_tap_listeners(); + + /* Update references in filters (if any) for the protocol + * tree corresponding to the currently selected frame in the GUI. */ + if (cf->edt != NULL && cf->edt->tree != NULL) { + if (dfcode) + dfilter_load_field_references(dfcode, cf->edt->tree); + if (filtering_tap_listeners) + tap_listeners_load_field_references(cf->edt); + } + + if (dfcode != NULL) { + dfilter_log_full(LOG_DOMAIN_DFILTER, LOG_LEVEL_NOISY, NULL, -1, NULL, + dfcode, "Rescanning packets with display filter"); + } + + /* Get the union of the flags for all tap listeners. */ + tap_flags = union_of_tap_listener_flags(); + + /* If the display filter or any tap listeners require the columns, + * construct them. */ + cinfo = (tap_listeners_require_columns() || + dfilter_requires_columns(dfcode)) ? &cf->cinfo : NULL; + + /* + * Determine whether we need to create a protocol tree. + * We do if: + * + * we're going to apply a display filter; + * + * one of the tap listeners is going to apply a filter; + * + * one of the tap listeners requires a protocol tree; + * + * we're redissecting and a postdissector wants field + * values or protocols on the first pass. + */ + create_proto_tree = + (dfcode != NULL || filtering_tap_listeners || + (tap_flags & TL_REQUIRES_PROTO_TREE) || + (redissect && postdissectors_want_hfids())); + + reset_tap_listeners(); + /* Which frame, if any, is the currently selected frame? + XXX - should the selected frame or the focus frame be the "current" + frame, that frame being the one from which "Find Frame" searches + start? */ + selected_frame = cf->current_frame; + + /* Mark frame num as not found */ + selected_frame_num = -1; + + /* Freeze the packet list while we redo it, so we don't get any + screen updates while it happens. */ + packet_list_freeze(); + + if (redissect) { + /* We need to re-initialize all the state information that protocols + keep, because some preference that controls a dissector has changed, + which might cause the state information to be constructed differently + by that dissector. */ + + /* We might receive new packets while redissecting, and we don't + want to dissect those before their time. */ + cf->redissecting = TRUE; + + /* 'reset' dissection session */ + epan_free(cf->epan); + if (cf->edt && cf->edt->pi.fd) { + /* All pointers in "per frame proto data" for the currently selected + packet are allocated in wmem_file_scope() and deallocated in epan_free(). + Free them here to avoid unintended usage in packet_list_clear(). */ + frame_data_destroy(cf->edt->pi.fd); + } + cf->epan = ws_epan_new(cf); + cf->cinfo.epan = cf->epan; + + /* A new Lua tap listener may be registered in lua_prime_all_fields() + called via epan_new() / init_dissection() when reloading Lua plugins. */ + if (!create_proto_tree && have_filtering_tap_listeners()) { + create_proto_tree = TRUE; + } + if (!cinfo && tap_listeners_require_columns()) { + cinfo = &cf->cinfo; + } + + /* We need to redissect the packets so we have to discard our old + * packet list store. */ + packet_list_clear(); + add_to_packet_list = TRUE; + } + + /* We don't yet know which will be the first and last frames displayed. */ + cf->first_displayed = 0; + cf->last_displayed = 0; + + /* We currently don't display any packets */ + cf->displayed_count = 0; + + /* Iterate through the list of frames. Call a routine for each frame + to check whether it should be displayed and, if so, add it to + the display list. */ + cf->provider.ref = NULL; + cf->provider.prev_dis = NULL; + cf->provider.prev_cap = NULL; + cf->cum_bytes = 0; + + cf_callback_invoke(cf_cb_file_rescan_started, cf); + + g_timer_start(prog_timer); + /* Count of packets at which we've looked. */ + count = 0; + /* Progress so far. */ + progbar_val = 0.0f; + + cf->stop_flag = FALSE; + start_time = g_get_monotonic_time(); + + /* no previous row yet */ + prev_frame_num = -1; + prev_frame = NULL; + + preceding_frame_num = -1; + preceding_frame = NULL; + following_frame_num = -1; + following_frame = NULL; + + selected_frame_seen = FALSE; + + frames_count = cf->count; + + epan_dissect_init(&edt, cf->epan, create_proto_tree, FALSE); + + if (redissect) { + /* + * Decryption secrets and name resolution blocks are read while + * sequentially processing records and then passed to the dissector. + * During redissection, the previous information is lost (see epan_free + * above), but they are not read again from the file as only packet + * records are re-read. Therefore reset the wtap secrets and name + * resolution callbacks such that wtap resupplies the callbacks with + * previously read information. + */ + wtap_set_cb_new_ipv4(cf->provider.wth, add_ipv4_name); + wtap_set_cb_new_ipv6(cf->provider.wth, (wtap_new_ipv6_callback_t) add_ipv6_name); + wtap_set_cb_new_secrets(cf->provider.wth, secrets_wtap_callback); + } + + for (framenum = 1; framenum <= frames_count; framenum++) { + fdata = frame_data_sequence_find(cf->provider.frames, framenum); + + /* Create the progress bar if necessary. + We check on every iteration of the loop, so that it takes no + longer than the standard time to create it (otherwise, for a + large file, we might take considerably longer than that standard + time in order to get to the next progress bar step). */ + if (progbar == NULL) + progbar = delayed_create_progress_dlg(cf->window, action, action_item, TRUE, + &cf->stop_flag, + progbar_val); + + /* + * Update the progress bar, but do it only after PROGBAR_UPDATE_INTERVAL + * has elapsed. Calling update_progress_dlg and packets_bar_update will + * likely trigger UI paint events, which might take a while depending on + * the platform and display. Reset our timer *after* painting. + */ + if (g_timer_elapsed(prog_timer, NULL) > PROGBAR_UPDATE_INTERVAL) { + /* let's not divide by zero. I should never be started + * with count == 0, so let's assert that + */ + ws_assert(cf->count > 0); + progbar_val = (gfloat) count / frames_count; + + if (progbar != NULL) { + snprintf(status_str, sizeof(status_str), + "%4u of %u frames", count, frames_count); + update_progress_dlg(progbar, progbar_val, status_str); + } + + g_timer_start(prog_timer); + } + + queued_rescan_type = cf->redissection_queued; + if (queued_rescan_type != RESCAN_NONE) { + /* A redissection was requested while an existing redissection was + * pending. */ + break; + } + + if (cf->stop_flag) { + /* Well, the user decided to abort the filtering. Just stop. + + XXX - go back to the previous filter? Users probably just + want not to wait for a filtering operation to finish; + unless we cancel by having no filter, reverting to the + previous filter will probably be even more expensive than + continuing the filtering, as it involves going back to the + beginning and filtering, and even with no filter we currently + have to re-generate the entire clist, which is also expensive. + + I'm not sure what Network Monitor does, but it doesn't appear + to give you an unfiltered display if you cancel. */ + break; + } + + count++; + + if (redissect) { + /* Since all state for the frame was destroyed, mark the frame + * as not visited, free the GSList referring to the state + * data (the per-frame data itself was freed by + * "init_dissection()"), and null out the GSList pointer. */ + frame_data_reset(fdata); + frames_count = cf->count; + } + + /* Frame dependencies from the previous dissection/filtering are no longer valid. */ + fdata->dependent_of_displayed = 0; + + if (!cf_read_record(cf, fdata, &rec, &buf)) + break; /* error reading the frame */ + + /* If the previous frame is displayed, and we haven't yet seen the + selected frame, remember that frame - it's the closest one we've + yet seen before the selected frame. */ + if (prev_frame_num != -1 && !selected_frame_seen && prev_frame->passed_dfilter) { + preceding_frame_num = prev_frame_num; + preceding_frame = prev_frame; + } + + add_packet_to_packet_list(fdata, cf, &edt, dfcode, + cinfo, &rec, &buf, + add_to_packet_list); + + /* If this frame is displayed, and this is the first frame we've + seen displayed after the selected frame, remember this frame - + it's the closest one we've yet seen at or after the selected + frame. */ + if (fdata->passed_dfilter && selected_frame_seen && following_frame_num == -1) { + following_frame_num = fdata->num; + following_frame = fdata; + } + if (fdata == selected_frame) { + selected_frame_seen = TRUE; + if (fdata->passed_dfilter) + selected_frame_num = fdata->num; + } + + /* Remember this frame - it'll be the previous frame + on the next pass through the loop. */ + prev_frame_num = fdata->num; + prev_frame = fdata; + wtap_rec_reset(&rec); + } + + epan_dissect_cleanup(&edt); + wtap_rec_cleanup(&rec); + ws_buffer_free(&buf); + + /* We are done redissecting the packet list. */ + cf->redissecting = FALSE; + + if (redissect) { + frames_count = cf->count; + /* Clear out what remains of the visited flags and per-frame data + pointers. + + XXX - that may cause various forms of bogosity when dissecting + these frames, as they won't have been seen by this sequential + pass, but the only alternative I see is to keep scanning them + even though the user requested that the scan stop, and that + would leave the user stuck with an Wireshark grinding on + until it finishes. Should we just stick them with that? */ + for (; framenum <= frames_count; framenum++) { + fdata = frame_data_sequence_find(cf->provider.frames, framenum); + frame_data_reset(fdata); + } + } + + /* We're done filtering the packets; destroy the progress bar if it + was created. */ + if (progbar != NULL) + destroy_progress_dlg(progbar); + g_timer_destroy(prog_timer); + + /* Unfreeze the packet list. */ + if (!add_to_packet_list) + packet_list_recreate_visible_rows(); + + /* Compute the time it took to filter the file */ + compute_elapsed(cf, start_time); + + packet_list_thaw(); + + /* It is safe again to execute redissections or sort. */ + ws_assert(cf->read_lock); + cf->read_lock = FALSE; + + cf_callback_invoke(cf_cb_file_rescan_finished, cf); + + if (selected_frame_num == -1) { + /* The selected frame didn't pass the filter. */ + if (selected_frame == NULL) { + /* That's because there *was* no selected frame. Make the first + displayed frame the current frame. */ + selected_frame_num = 0; + } else { + /* Find the nearest displayed frame to the selected frame (whether + it's before or after that frame) and make that the current frame. + If the next and previous displayed frames are equidistant from the + selected frame, choose the next one. */ + ws_assert(following_frame == NULL || + following_frame->num >= selected_frame->num); + ws_assert(preceding_frame == NULL || + preceding_frame->num <= selected_frame->num); + if (following_frame == NULL) { + /* No frame after the selected frame passed the filter, so we + have to select the last displayed frame before the selected + frame. */ + selected_frame_num = preceding_frame_num; + selected_frame = preceding_frame; + } else if (preceding_frame == NULL) { + /* No frame before the selected frame passed the filter, so we + have to select the first displayed frame after the selected + frame. */ + selected_frame_num = following_frame_num; + selected_frame = following_frame; + } else { + /* Frames before and after the selected frame passed the filter, so + we'll select the previous frame */ + selected_frame_num = preceding_frame_num; + selected_frame = preceding_frame; + } + } + } + + if (selected_frame_num == -1) { + /* There are no frames displayed at all. */ + cf_unselect_packet(cf); + } else { + /* Either the frame that was selected passed the filter, or we've + found the nearest displayed frame to that frame. Select it, make + it the focus row, and make it visible. */ + /* Set to invalid to force update of packet list and packet details */ + if (selected_frame_num == 0) { + packet_list_select_row_from_data(NULL); + }else{ + if (!packet_list_select_row_from_data(selected_frame)) { + /* We didn't find a row corresponding to this frame. + This means that the frame isn't being displayed currently, + so we can't select it. */ + simple_message_box(ESD_TYPE_INFO, NULL, + "The capture file is probably not fully dissected.", + "End of capture exceeded."); + } + } + } + + /* Cleanup and release all dfilter resources */ + dfilter_free(dfcode); + + /* If another rescan (due to dfilter change) or redissection (due to profile + * change) was requested, the rescan above is aborted and restarted here. */ + if (queued_rescan_type != RESCAN_NONE) { + redissect = redissect || queued_rescan_type == RESCAN_REDISSECT; + rescan_packets(cf, "Reprocessing", "all packets", redissect); + } +} + + +/* + * Scan through all frame data and recalculate the ref time + * without rereading the file. + * XXX - do we need a progress bar or is this fast enough? + */ +void +cf_reftime_packets(capture_file* cf) +{ + guint32 framenum; + frame_data *fdata; + nstime_t rel_ts; + + cf->provider.ref = NULL; + cf->provider.prev_dis = NULL; + cf->cum_bytes = 0; + + for (framenum = 1; framenum <= cf->count; framenum++) { + fdata = frame_data_sequence_find(cf->provider.frames, framenum); + + /* just add some value here until we know if it is being displayed or not */ + fdata->cum_bytes = cf->cum_bytes + fdata->pkt_len; + + /* + * Timestamps + */ + + if (fdata->has_ts) { + /* If we don't have the time stamp of the first packet in the + capture, it's because this is the first packet. Save the time + stamp of this packet as the time stamp of the first packet. */ + if (cf->provider.ref == NULL) + cf->provider.ref = fdata; + /* if this frames is marked as a reference time frame, reset + firstsec and firstusec to this frame */ + if (fdata->ref_time) + cf->provider.ref = fdata; + + /* Get the time elapsed between the first packet and this one. */ + fdata->frame_ref_num = (fdata != cf->provider.ref) ? cf->provider.ref->num : 0; + nstime_delta(&rel_ts, &fdata->abs_ts, &cf->provider.ref->abs_ts); + + /* If it's greater than the current elapsed time, set the elapsed + time to it (we check for "greater than" so as not to be + confused by time moving backwards). */ + if ((gint32)cf->elapsed_time.secs < rel_ts.secs + || ((gint32)cf->elapsed_time.secs == rel_ts.secs && (gint32)cf->elapsed_time.nsecs < rel_ts.nsecs)) { + cf->elapsed_time = rel_ts; + } + + /* If this frame is displayed, get the time elapsed between the + previous displayed packet and this packet. */ + /* XXX: What if in the future we want to use the previously + * displayed frame for something else, too? Then we'd want + * to store this frame as prev_dis even if it doesn't have a + * timestamp. */ + if ( fdata->passed_dfilter ) { + /* If we don't have the time stamp of the previous displayed + packet, it's because this is the first displayed packet. + Save the time stamp of this packet as the time stamp of + the previous displayed packet. */ + if (cf->provider.prev_dis == NULL) { + cf->provider.prev_dis = fdata; + } + + fdata->prev_dis_num = cf->provider.prev_dis->num; + cf->provider.prev_dis = fdata; + } + } else { + /* If this frame doesn't have a timestamp, don't calculate + anything with relative times. */ + /* However, if this frame is marked as a reference time frame, + clear the reference frame so that the next frame with a + timestamp becomes the reference frame. */ + if (fdata->ref_time) { + cf->provider.ref = NULL; + } + } + + /* + * Byte counts + */ + if ( (fdata->passed_dfilter) || (fdata->ref_time) ) { + /* This frame either passed the display filter list or is marked as + a time reference frame. All time reference frames are displayed + even if they don't pass the display filter */ + if (fdata->ref_time) { + /* if this was a TIME REF frame we should reset the cum_bytes field */ + cf->cum_bytes = fdata->pkt_len; + fdata->cum_bytes = cf->cum_bytes; + } else { + /* increase cum_bytes with this packets length */ + cf->cum_bytes += fdata->pkt_len; + } + } + } +} + +typedef enum { + PSP_FINISHED, + PSP_STOPPED, + PSP_FAILED +} psp_return_t; + +static psp_return_t +process_specified_records(capture_file *cf, packet_range_t *range, + const char *string1, const char *string2, gboolean terminate_is_stop, + gboolean (*callback)(capture_file *, frame_data *, + wtap_rec *, Buffer *, void *), + void *callback_args, + gboolean show_progress_bar) +{ + guint32 framenum; + frame_data *fdata; + wtap_rec rec; + Buffer buf; + psp_return_t ret = PSP_FINISHED; + + progdlg_t *progbar = NULL; + GTimer *prog_timer = g_timer_new(); + int progbar_count; + float progbar_val; + gchar progbar_status_str[100]; + range_process_e process_this; + + wtap_rec_init(&rec); + ws_buffer_init(&buf, 1514); + + g_timer_start(prog_timer); + /* Count of packets at which we've looked. */ + progbar_count = 0; + /* Progress so far. */ + progbar_val = 0.0f; + + if (cf->read_lock) { + ws_warning("Failing due to nested process_specified_records(\"%s\") call!", cf->filename); + return PSP_FAILED; + } + cf->read_lock = TRUE; + + cf->stop_flag = FALSE; + + if (range != NULL) + packet_range_process_init(range); + + /* Iterate through all the packets, printing the packets that + were selected by the current display filter. */ + for (framenum = 1; framenum <= cf->count; framenum++) { + fdata = frame_data_sequence_find(cf->provider.frames, framenum); + + /* Create the progress bar if necessary. + We check on every iteration of the loop, so that it takes no + longer than the standard time to create it (otherwise, for a + large file, we might take considerably longer than that standard + time in order to get to the next progress bar step). */ + if (show_progress_bar && progbar == NULL) + progbar = delayed_create_progress_dlg(cf->window, string1, string2, + terminate_is_stop, + &cf->stop_flag, + progbar_val); + + /* + * Update the progress bar, but do it only after PROGBAR_UPDATE_INTERVAL + * has elapsed. Calling update_progress_dlg and packets_bar_update will + * likely trigger UI paint events, which might take a while depending on + * the platform and display. Reset our timer *after* painting. + */ + if (progbar && g_timer_elapsed(prog_timer, NULL) > PROGBAR_UPDATE_INTERVAL) { + /* let's not divide by zero. I should never be started + * with count == 0, so let's assert that + */ + ws_assert(cf->count > 0); + progbar_val = (gfloat) progbar_count / cf->count; + + snprintf(progbar_status_str, sizeof(progbar_status_str), + "%4u of %u packets", progbar_count, cf->count); + update_progress_dlg(progbar, progbar_val, progbar_status_str); + + g_timer_start(prog_timer); + } + + if (cf->stop_flag) { + /* Well, the user decided to abort the operation. Just stop, + and arrange to return PSP_STOPPED to our caller, so they know + it was stopped explicitly. */ + ret = PSP_STOPPED; + break; + } + + progbar_count++; + + if (range != NULL) { + /* do we have to process this packet? */ + process_this = packet_range_process_packet(range, fdata); + if (process_this == range_process_next) { + /* this packet uninteresting, continue with next one */ + continue; + } else if (process_this == range_processing_finished) { + /* all interesting packets processed, stop the loop */ + break; + } + } + + /* Get the packet */ + if (!cf_read_record(cf, fdata, &rec, &buf)) { + /* Attempt to get the packet failed. */ + ret = PSP_FAILED; + break; + } + /* Process the packet */ + if (!callback(cf, fdata, &rec, &buf, callback_args)) { + /* Callback failed. We assume it reported the error appropriately. */ + ret = PSP_FAILED; + break; + } + wtap_rec_reset(&rec); + } + + /* We're done printing the packets; destroy the progress bar if + it was created. */ + if (progbar != NULL) + destroy_progress_dlg(progbar); + g_timer_destroy(prog_timer); + + ws_assert(cf->read_lock); + cf->read_lock = FALSE; + + wtap_rec_cleanup(&rec); + ws_buffer_free(&buf); + + return ret; +} + +typedef struct { + epan_dissect_t edt; + column_info *cinfo; +} retap_callback_args_t; + +static gboolean +retap_packet(capture_file *cf, frame_data *fdata, wtap_rec *rec, Buffer *buf, + void *argsp) +{ + retap_callback_args_t *args = (retap_callback_args_t *)argsp; + + epan_dissect_run_with_taps(&args->edt, cf->cd_t, rec, + frame_tvbuff_new_buffer(&cf->provider, fdata, buf), + fdata, args->cinfo); + epan_dissect_reset(&args->edt); + + return TRUE; +} + +cf_read_status_t +cf_retap_packets(capture_file *cf) +{ + packet_range_t range; + retap_callback_args_t callback_args; + gboolean create_proto_tree; + gboolean filtering_tap_listeners; + guint tap_flags; + psp_return_t ret; + + /* Presumably the user closed the capture file. */ + if (cf == NULL) { + return CF_READ_ABORTED; + } + + cf_callback_invoke(cf_cb_file_retap_started, cf); + + /* Do we have any tap listeners with filters? */ + filtering_tap_listeners = have_filtering_tap_listeners(); + + /* Update references in filters (if any) for the protocol + * tree corresponding to the currently selected frame in the GUI. */ + if (cf->edt != NULL && cf->edt->tree != NULL) { + if (filtering_tap_listeners) + tap_listeners_load_field_references(cf->edt); + } + + /* Get the union of the flags for all tap listeners. */ + tap_flags = union_of_tap_listener_flags(); + + /* If any tap listeners require the columns, construct them. */ + callback_args.cinfo = (tap_listeners_require_columns()) ? &cf->cinfo : NULL; + + /* + * Determine whether we need to create a protocol tree. + * We do if: + * + * one of the tap listeners is going to apply a filter; + * + * one of the tap listeners requires a protocol tree. + */ + create_proto_tree = + (filtering_tap_listeners || (tap_flags & TL_REQUIRES_PROTO_TREE)); + + /* Reset the tap listeners. */ + reset_tap_listeners(); + + epan_dissect_init(&callback_args.edt, cf->epan, create_proto_tree, FALSE); + + /* Iterate through the list of packets, dissecting all packets and + re-running the taps. */ + packet_range_init(&range, cf); + packet_range_process_init(&range); + + ret = process_specified_records(cf, &range, "Recalculating statistics on", + "all packets", TRUE, retap_packet, + &callback_args, TRUE); + + packet_range_cleanup(&range); + epan_dissect_cleanup(&callback_args.edt); + + cf_callback_invoke(cf_cb_file_retap_finished, cf); + + switch (ret) { + case PSP_FINISHED: + /* Completed successfully. */ + return CF_READ_OK; + + case PSP_STOPPED: + /* Well, the user decided to abort the refiltering. + Return CF_READ_ABORTED so our caller knows they did that. */ + return CF_READ_ABORTED; + + case PSP_FAILED: + /* Error while retapping. */ + return CF_READ_ERROR; + } + + ws_assert_not_reached(); + return CF_READ_OK; +} + +typedef struct { + print_args_t *print_args; + gboolean print_header_line; + char *header_line_buf; + int header_line_buf_len; + gboolean print_formfeed; + gboolean print_separator; + char *line_buf; + int line_buf_len; + gint *col_widths; + int num_visible_cols; + gint *visible_cols; + epan_dissect_t edt; +} print_callback_args_t; + +static gboolean +print_packet(capture_file *cf, frame_data *fdata, wtap_rec *rec, Buffer *buf, + void *argsp) +{ + print_callback_args_t *args = (print_callback_args_t *)argsp; + int i; + char *cp; + int line_len; + int column_len; + int cp_off; + char bookmark_name[9+10+1]; /* "__frameNNNNNNNNNN__\0" */ + char bookmark_title[6+10+1]; /* "Frame NNNNNNNNNN__\0" */ + col_item_t* col_item; + const gchar* col_text; + + /* Fill in the column information if we're printing the summary + information. */ + if (args->print_args->print_summary) { + col_custom_prime_edt(&args->edt, &cf->cinfo); + epan_dissect_run(&args->edt, cf->cd_t, rec, + frame_tvbuff_new_buffer(&cf->provider, fdata, buf), + fdata, &cf->cinfo); + epan_dissect_fill_in_columns(&args->edt, FALSE, TRUE); + } else + epan_dissect_run(&args->edt, cf->cd_t, rec, + frame_tvbuff_new_buffer(&cf->provider, fdata, buf), + fdata, NULL); + + if (args->print_formfeed) { + if (!new_page(args->print_args->stream)) + goto fail; + + /* + * Print another header line if we print a packet summary on the + * new page. + */ + if (args->print_args->print_col_headings) + args->print_header_line = TRUE; + } else { + if (args->print_separator) { + if (!print_line(args->print_args->stream, 0, "")) + goto fail; + } + } + + /* + * We generate bookmarks, if the output format supports them. + * The name is "__frameN__". + */ + snprintf(bookmark_name, sizeof bookmark_name, "__frame%u__", fdata->num); + + if (args->print_args->print_summary) { + if (!args->print_args->print_col_headings) + args->print_header_line = FALSE; + if (args->print_header_line) { + if (!print_line(args->print_args->stream, 0, args->header_line_buf)) + goto fail; + args->print_header_line = FALSE; /* we might not need to print any more */ + } + cp = &args->line_buf[0]; + line_len = 0; + for (i = 0; i < args->num_visible_cols; i++) { + col_item = &cf->cinfo.columns[args->visible_cols[i]]; + col_text = get_column_text(&cf->cinfo, args->visible_cols[i]); + /* Find the length of the string for this column. */ + column_len = (int) strlen(col_text); + if (args->col_widths[i] > column_len) + column_len = args->col_widths[i]; + + /* Make sure there's room in the line buffer for the column; if not, + double its length. */ + line_len += column_len + 1; /* "+1" for space */ + if (line_len > args->line_buf_len) { + cp_off = (int) (cp - args->line_buf); + args->line_buf_len = 2 * line_len; + args->line_buf = (char *)g_realloc(args->line_buf, args->line_buf_len + 1); + cp = args->line_buf + cp_off; + } + + /* Right-justify the packet number column. */ + if (col_item->col_fmt == COL_NUMBER) + snprintf(cp, column_len+1, "%*s", args->col_widths[i], col_text); + else + snprintf(cp, column_len+1, "%-*s", args->col_widths[i], col_text); + cp += column_len; + if (i != args->num_visible_cols - 1) + *cp++ = ' '; + } + *cp = '\0'; + + /* + * Generate a bookmark, using the summary line as the title. + */ + if (!print_bookmark(args->print_args->stream, bookmark_name, + args->line_buf)) + goto fail; + + if (!print_line(args->print_args->stream, 0, args->line_buf)) + goto fail; + } else { + /* + * Generate a bookmark, using "Frame N" as the title, as we're not + * printing the summary line. + */ + snprintf(bookmark_title, sizeof bookmark_title, "Frame %u", fdata->num); + if (!print_bookmark(args->print_args->stream, bookmark_name, + bookmark_title)) + goto fail; + } /* if (print_summary) */ + + if (args->print_args->print_dissections != print_dissections_none) { + if (args->print_args->print_summary) { + /* Separate the summary line from the tree with a blank line. */ + if (!print_line(args->print_args->stream, 0, "")) + goto fail; + } + + /* Print the information in that tree. */ + if (!proto_tree_print(args->print_args->print_dissections, + args->print_args->print_hex, &args->edt, NULL, + args->print_args->stream)) + goto fail; + + /* Print a blank line if we print anything after this (aka more than one packet). */ + args->print_separator = TRUE; + + /* Print a header line if we print any more packet summaries */ + if (args->print_args->print_col_headings) + args->print_header_line = TRUE; + } + + if (args->print_args->print_hex) { + if (args->print_args->print_summary || (args->print_args->print_dissections != print_dissections_none)) { + if (!print_line(args->print_args->stream, 0, "")) + goto fail; + } + /* Print the full packet data as hex. */ + if (!print_hex_data(args->print_args->stream, &args->edt, args->print_args->hexdump_options)) + goto fail; + + /* Print a blank line if we print anything after this (aka more than one packet). */ + args->print_separator = TRUE; + + /* Print a header line if we print any more packet summaries */ + if (args->print_args->print_col_headings) + args->print_header_line = TRUE; + } /* if (args->print_args->print_dissections != print_dissections_none) */ + + epan_dissect_reset(&args->edt); + + /* do we want to have a formfeed between each packet from now on? */ + if (args->print_args->print_formfeed) { + args->print_formfeed = TRUE; + } + + return TRUE; + +fail: + epan_dissect_reset(&args->edt); + return FALSE; +} + +cf_print_status_t +cf_print_packets(capture_file *cf, print_args_t *print_args, + gboolean show_progress_bar) +{ + print_callback_args_t callback_args; + gint data_width; + char *cp; + int i, cp_off, column_len, line_len; + int num_visible_col = 0, last_visible_col = 0, visible_col_count; + psp_return_t ret; + GList *clp; + fmt_data *cfmt; + gboolean proto_tree_needed; + + callback_args.print_args = print_args; + callback_args.print_header_line = print_args->print_col_headings; + callback_args.header_line_buf = NULL; + callback_args.header_line_buf_len = 256; + callback_args.print_formfeed = FALSE; + callback_args.print_separator = FALSE; + callback_args.line_buf = NULL; + callback_args.line_buf_len = 256; + callback_args.col_widths = NULL; + callback_args.num_visible_cols = 0; + callback_args.visible_cols = NULL; + + if (!print_preamble(print_args->stream, cf->filename, get_ws_vcs_version_info())) { + destroy_print_stream(print_args->stream); + return CF_PRINT_WRITE_ERROR; + } + + if (print_args->print_summary) { + /* We're printing packet summaries. Allocate the header line buffer + and get the column widths. */ + callback_args.header_line_buf = (char *)g_malloc(callback_args.header_line_buf_len + 1); + + /* Find the number of visible columns and the last visible column */ + for (i = 0; i < prefs.num_cols; i++) { + + clp = g_list_nth(prefs.col_list, i); + if (clp == NULL) /* Sanity check, Invalid column requested */ + continue; + + cfmt = (fmt_data *) clp->data; + if (cfmt->visible) { + num_visible_col++; + last_visible_col = i; + } + } + + /* if num_visible_col is 0, we are done */ + if (num_visible_col == 0) { + g_free(callback_args.header_line_buf); + return CF_PRINT_OK; + } + + /* Find the widths for each of the columns - maximum of the + width of the title and the width of the data - and construct + a buffer with a line containing the column titles. */ + callback_args.num_visible_cols = num_visible_col; + callback_args.col_widths = g_new(gint, num_visible_col); + callback_args.visible_cols = g_new(gint, num_visible_col); + cp = &callback_args.header_line_buf[0]; + line_len = 0; + visible_col_count = 0; + for (i = 0; i < cf->cinfo.num_cols; i++) { + + clp = g_list_nth(prefs.col_list, i); + if (clp == NULL) /* Sanity check, Invalid column requested */ + continue; + + cfmt = (fmt_data *) clp->data; + if (cfmt->visible == FALSE) + continue; + + /* Save the order of visible columns */ + callback_args.visible_cols[visible_col_count] = i; + + /* Don't pad the last column. */ + if (i == last_visible_col) + callback_args.col_widths[visible_col_count] = 0; + else { + callback_args.col_widths[visible_col_count] = (gint) strlen(cf->cinfo.columns[i].col_title); + data_width = get_column_char_width(get_column_format(i)); + if (data_width > callback_args.col_widths[visible_col_count]) + callback_args.col_widths[visible_col_count] = data_width; + } + + /* Find the length of the string for this column. */ + column_len = (int) strlen(cf->cinfo.columns[i].col_title); + if (callback_args.col_widths[visible_col_count] > column_len) + column_len = callback_args.col_widths[visible_col_count]; + + /* Make sure there's room in the line buffer for the column; if not, + double its length. */ + line_len += column_len + 1; /* "+1" for space */ + if (line_len > callback_args.header_line_buf_len) { + cp_off = (int) (cp - callback_args.header_line_buf); + callback_args.header_line_buf_len = 2 * line_len; + callback_args.header_line_buf = (char *)g_realloc(callback_args.header_line_buf, + callback_args.header_line_buf_len + 1); + cp = callback_args.header_line_buf + cp_off; + } + + /* Right-justify the packet number column. */ +/* if (cf->cinfo.col_fmt[i] == COL_NUMBER) + snprintf(cp, column_len+1, "%*s", callback_args.col_widths[visible_col_count], cf->cinfo.columns[i].col_title); + else*/ + snprintf(cp, column_len+1, "%-*s", callback_args.col_widths[visible_col_count], cf->cinfo.columns[i].col_title); + cp += column_len; + if (i != cf->cinfo.num_cols - 1) + *cp++ = ' '; + + visible_col_count++; + } + *cp = '\0'; + + /* Now start out the main line buffer with the same length as the + header line buffer. */ + callback_args.line_buf_len = callback_args.header_line_buf_len; + callback_args.line_buf = (char *)g_malloc(callback_args.line_buf_len + 1); + } /* if (print_summary) */ + + /* Create the protocol tree, and make it visible, if we're printing + the dissection or the hex data. + XXX - do we need it if we're just printing the hex data? */ + proto_tree_needed = + callback_args.print_args->print_dissections != print_dissections_none || + callback_args.print_args->print_hex || + have_custom_cols(&cf->cinfo) || have_field_extractors(); + epan_dissect_init(&callback_args.edt, cf->epan, proto_tree_needed, proto_tree_needed); + + /* Iterate through the list of packets, printing the packets we were + told to print. */ + ret = process_specified_records(cf, &print_args->range, "Printing", + "selected packets", TRUE, print_packet, + &callback_args, show_progress_bar); + epan_dissect_cleanup(&callback_args.edt); + g_free(callback_args.header_line_buf); + g_free(callback_args.line_buf); + g_free(callback_args.col_widths); + g_free(callback_args.visible_cols); + + switch (ret) { + + case PSP_FINISHED: + /* Completed successfully. */ + break; + + case PSP_STOPPED: + /* Well, the user decided to abort the printing. + + XXX - note that what got generated before they did that + will get printed if we're piping to a print program; we'd + have to write to a file and then hand that to the print + program to make it actually not print anything. */ + break; + + case PSP_FAILED: + /* Error while printing. + + XXX - note that what got generated before they did that + will get printed if we're piping to a print program; we'd + have to write to a file and then hand that to the print + program to make it actually not print anything. */ + destroy_print_stream(print_args->stream); + return CF_PRINT_WRITE_ERROR; + } + + if (!print_finale(print_args->stream)) { + destroy_print_stream(print_args->stream); + return CF_PRINT_WRITE_ERROR; + } + + if (!destroy_print_stream(print_args->stream)) + return CF_PRINT_WRITE_ERROR; + + return CF_PRINT_OK; +} + +typedef struct { + FILE *fh; + epan_dissect_t edt; + print_args_t *print_args; + json_dumper jdumper; +} write_packet_callback_args_t; + +static gboolean +write_pdml_packet(capture_file *cf, frame_data *fdata, wtap_rec *rec, + Buffer *buf, void *argsp) +{ + write_packet_callback_args_t *args = (write_packet_callback_args_t *)argsp; + + /* Create the protocol tree, but don't fill in the column information. */ + epan_dissect_run(&args->edt, cf->cd_t, rec, + frame_tvbuff_new_buffer(&cf->provider, fdata, buf), + fdata, NULL); + + /* Write out the information in that tree. */ + write_pdml_proto_tree(NULL, &args->edt, &cf->cinfo, args->fh, FALSE); + + epan_dissect_reset(&args->edt); + + return !ferror(args->fh); +} + +cf_print_status_t +cf_write_pdml_packets(capture_file *cf, print_args_t *print_args) +{ + write_packet_callback_args_t callback_args; + FILE *fh; + psp_return_t ret; + + fh = ws_fopen(print_args->file, "w"); + if (fh == NULL) + return CF_PRINT_OPEN_ERROR; /* attempt to open destination failed */ + + write_pdml_preamble(fh, cf->filename); + if (ferror(fh)) { + fclose(fh); + return CF_PRINT_WRITE_ERROR; + } + + callback_args.fh = fh; + callback_args.print_args = print_args; + epan_dissect_init(&callback_args.edt, cf->epan, TRUE, TRUE); + + /* Iterate through the list of packets, printing the packets we were + told to print. */ + ret = process_specified_records(cf, &print_args->range, "Writing PDML", + "selected packets", TRUE, + write_pdml_packet, &callback_args, TRUE); + + epan_dissect_cleanup(&callback_args.edt); + + switch (ret) { + + case PSP_FINISHED: + /* Completed successfully. */ + break; + + case PSP_STOPPED: + /* Well, the user decided to abort the printing. */ + break; + + case PSP_FAILED: + /* Error while printing. */ + fclose(fh); + return CF_PRINT_WRITE_ERROR; + } + + write_pdml_finale(fh); + if (ferror(fh)) { + fclose(fh); + return CF_PRINT_WRITE_ERROR; + } + + /* XXX - check for an error */ + fclose(fh); + + return CF_PRINT_OK; +} + +static gboolean +write_psml_packet(capture_file *cf, frame_data *fdata, wtap_rec *rec, + Buffer *buf, void *argsp) +{ + write_packet_callback_args_t *args = (write_packet_callback_args_t *)argsp; + + /* Fill in the column information */ + col_custom_prime_edt(&args->edt, &cf->cinfo); + epan_dissect_run(&args->edt, cf->cd_t, rec, + frame_tvbuff_new_buffer(&cf->provider, fdata, buf), + fdata, &cf->cinfo); + epan_dissect_fill_in_columns(&args->edt, FALSE, TRUE); + + /* Write out the column information. */ + write_psml_columns(&args->edt, args->fh, FALSE); + + epan_dissect_reset(&args->edt); + + return !ferror(args->fh); +} + +cf_print_status_t +cf_write_psml_packets(capture_file *cf, print_args_t *print_args) +{ + write_packet_callback_args_t callback_args; + FILE *fh; + psp_return_t ret; + + gboolean proto_tree_needed; + + fh = ws_fopen(print_args->file, "w"); + if (fh == NULL) + return CF_PRINT_OPEN_ERROR; /* attempt to open destination failed */ + + write_psml_preamble(&cf->cinfo, fh); + if (ferror(fh)) { + fclose(fh); + return CF_PRINT_WRITE_ERROR; + } + + callback_args.fh = fh; + callback_args.print_args = print_args; + + /* Fill in the column information, only create the protocol tree + if having custom columns or field extractors. */ + proto_tree_needed = have_custom_cols(&cf->cinfo) || have_field_extractors(); + epan_dissect_init(&callback_args.edt, cf->epan, proto_tree_needed, proto_tree_needed); + + /* Iterate through the list of packets, printing the packets we were + told to print. */ + ret = process_specified_records(cf, &print_args->range, "Writing PSML", + "selected packets", TRUE, + write_psml_packet, &callback_args, TRUE); + + epan_dissect_cleanup(&callback_args.edt); + + switch (ret) { + + case PSP_FINISHED: + /* Completed successfully. */ + break; + + case PSP_STOPPED: + /* Well, the user decided to abort the printing. */ + break; + + case PSP_FAILED: + /* Error while printing. */ + fclose(fh); + return CF_PRINT_WRITE_ERROR; + } + + write_psml_finale(fh); + if (ferror(fh)) { + fclose(fh); + return CF_PRINT_WRITE_ERROR; + } + + /* XXX - check for an error */ + fclose(fh); + + return CF_PRINT_OK; +} + +static gboolean +write_csv_packet(capture_file *cf, frame_data *fdata, wtap_rec *rec, + Buffer *buf, void *argsp) +{ + write_packet_callback_args_t *args = (write_packet_callback_args_t *)argsp; + + /* Fill in the column information */ + col_custom_prime_edt(&args->edt, &cf->cinfo); + epan_dissect_run(&args->edt, cf->cd_t, rec, + frame_tvbuff_new_buffer(&cf->provider, fdata, buf), + fdata, &cf->cinfo); + epan_dissect_fill_in_columns(&args->edt, FALSE, TRUE); + + /* Write out the column information. */ + write_csv_columns(&args->edt, args->fh); + + epan_dissect_reset(&args->edt); + + return !ferror(args->fh); +} + +cf_print_status_t +cf_write_csv_packets(capture_file *cf, print_args_t *print_args) +{ + write_packet_callback_args_t callback_args; + gboolean proto_tree_needed; + FILE *fh; + psp_return_t ret; + + fh = ws_fopen(print_args->file, "w"); + if (fh == NULL) + return CF_PRINT_OPEN_ERROR; /* attempt to open destination failed */ + + write_csv_column_titles(&cf->cinfo, fh); + if (ferror(fh)) { + fclose(fh); + return CF_PRINT_WRITE_ERROR; + } + + callback_args.fh = fh; + callback_args.print_args = print_args; + + /* only create the protocol tree if having custom columns or field extractors. */ + proto_tree_needed = have_custom_cols(&cf->cinfo) || have_field_extractors(); + epan_dissect_init(&callback_args.edt, cf->epan, proto_tree_needed, proto_tree_needed); + + /* Iterate through the list of packets, printing the packets we were + told to print. */ + ret = process_specified_records(cf, &print_args->range, "Writing CSV", + "selected packets", TRUE, + write_csv_packet, &callback_args, TRUE); + + epan_dissect_cleanup(&callback_args.edt); + + switch (ret) { + + case PSP_FINISHED: + /* Completed successfully. */ + break; + + case PSP_STOPPED: + /* Well, the user decided to abort the printing. */ + break; + + case PSP_FAILED: + /* Error while printing. */ + fclose(fh); + return CF_PRINT_WRITE_ERROR; + } + + /* XXX - check for an error */ + fclose(fh); + + return CF_PRINT_OK; +} + +static gboolean +carrays_write_packet(capture_file *cf, frame_data *fdata, wtap_rec *rec, + Buffer *buf, void *argsp) +{ + write_packet_callback_args_t *args = (write_packet_callback_args_t *)argsp; + + epan_dissect_run(&args->edt, cf->cd_t, rec, + frame_tvbuff_new_buffer(&cf->provider, fdata, buf), + fdata, NULL); + write_carrays_hex_data(fdata->num, args->fh, &args->edt); + epan_dissect_reset(&args->edt); + + return !ferror(args->fh); +} + +cf_print_status_t +cf_write_carrays_packets(capture_file *cf, print_args_t *print_args) +{ + write_packet_callback_args_t callback_args; + FILE *fh; + psp_return_t ret; + + fh = ws_fopen(print_args->file, "w"); + + if (fh == NULL) + return CF_PRINT_OPEN_ERROR; /* attempt to open destination failed */ + + if (ferror(fh)) { + fclose(fh); + return CF_PRINT_WRITE_ERROR; + } + + callback_args.fh = fh; + callback_args.print_args = print_args; + epan_dissect_init(&callback_args.edt, cf->epan, TRUE, TRUE); + + /* Iterate through the list of packets, printing the packets we were + told to print. */ + ret = process_specified_records(cf, &print_args->range, + "Writing C Arrays", + "selected packets", TRUE, + carrays_write_packet, &callback_args, TRUE); + + epan_dissect_cleanup(&callback_args.edt); + + switch (ret) { + case PSP_FINISHED: + /* Completed successfully. */ + break; + case PSP_STOPPED: + /* Well, the user decided to abort the printing. */ + break; + case PSP_FAILED: + /* Error while printing. */ + fclose(fh); + return CF_PRINT_WRITE_ERROR; + } + + fclose(fh); + return CF_PRINT_OK; +} + +static gboolean +write_json_packet(capture_file *cf, frame_data *fdata, wtap_rec *rec, + Buffer *buf, void *argsp) +{ + write_packet_callback_args_t *args = (write_packet_callback_args_t *)argsp; + + /* Create the protocol tree, but don't fill in the column information. */ + epan_dissect_run(&args->edt, cf->cd_t, rec, + frame_tvbuff_new_buffer(&cf->provider, fdata, buf), + fdata, NULL); + + /* Write out the information in that tree. */ + write_json_proto_tree(NULL, args->print_args->print_dissections, + args->print_args->print_hex, + &args->edt, &cf->cinfo, proto_node_group_children_by_unique, + &args->jdumper); + + epan_dissect_reset(&args->edt); + + return !ferror(args->fh); +} + +cf_print_status_t +cf_write_json_packets(capture_file *cf, print_args_t *print_args) +{ + write_packet_callback_args_t callback_args; + FILE *fh; + psp_return_t ret; + + fh = ws_fopen(print_args->file, "w"); + if (fh == NULL) + return CF_PRINT_OPEN_ERROR; /* attempt to open destination failed */ + + callback_args.jdumper = write_json_preamble(fh); + if (ferror(fh)) { + fclose(fh); + return CF_PRINT_WRITE_ERROR; + } + + callback_args.fh = fh; + callback_args.print_args = print_args; + epan_dissect_init(&callback_args.edt, cf->epan, TRUE, TRUE); + + /* Iterate through the list of packets, printing the packets we were + told to print. */ + ret = process_specified_records(cf, &print_args->range, "Writing JSON", + "selected packets", TRUE, + write_json_packet, &callback_args, TRUE); + + epan_dissect_cleanup(&callback_args.edt); + + switch (ret) { + + case PSP_FINISHED: + /* Completed successfully. */ + break; + + case PSP_STOPPED: + /* Well, the user decided to abort the printing. */ + break; + + case PSP_FAILED: + /* Error while printing. */ + fclose(fh); + return CF_PRINT_WRITE_ERROR; + } + + write_json_finale(&callback_args.jdumper); + if (ferror(fh)) { + fclose(fh); + return CF_PRINT_WRITE_ERROR; + } + + /* XXX - check for an error */ + fclose(fh); + + return CF_PRINT_OK; +} + +gboolean +cf_find_packet_protocol_tree(capture_file *cf, const char *string, + search_direction dir) +{ + match_data mdata; + + mdata.string = string; + mdata.string_len = strlen(string); + return find_packet(cf, match_protocol_tree, &mdata, dir); +} + +field_info* +cf_find_string_protocol_tree(capture_file *cf, proto_tree *tree) +{ + match_data mdata; + mdata.frame_matched = FALSE; + mdata.string = convert_string_case(cf->sfilter, cf->case_type); + mdata.string_len = strlen(mdata.string); + mdata.cf = cf; + /* Iterate through all the nodes looking for matching text */ + proto_tree_children_foreach(tree, match_subtree_text, &mdata); + g_free((char *)mdata.string); + return mdata.frame_matched ? mdata.finfo : NULL; +} + +static match_result +match_protocol_tree(capture_file *cf, frame_data *fdata, + wtap_rec *rec, Buffer *buf, void *criterion) +{ + match_data *mdata = (match_data *)criterion; + epan_dissect_t edt; + + /* Load the frame's data. */ + if (!cf_read_record(cf, fdata, rec, buf)) { + /* Attempt to get the packet failed. */ + return MR_ERROR; + } + + /* Construct the protocol tree, including the displayed text */ + epan_dissect_init(&edt, cf->epan, TRUE, TRUE); + /* We don't need the column information */ + epan_dissect_run(&edt, cf->cd_t, rec, + frame_tvbuff_new_buffer(&cf->provider, fdata, buf), + fdata, NULL); + + /* Iterate through all the nodes, seeing if they have text that matches. */ + mdata->cf = cf; + mdata->frame_matched = FALSE; + proto_tree_children_foreach(edt.tree, match_subtree_text, mdata); + epan_dissect_cleanup(&edt); + return mdata->frame_matched ? MR_MATCHED : MR_NOTMATCHED; +} + +static void +match_subtree_text(proto_node *node, gpointer data) +{ + match_data *mdata = (match_data *) data; + const gchar *string = mdata->string; + size_t string_len = mdata->string_len; + capture_file *cf = mdata->cf; + field_info *fi = PNODE_FINFO(node); + gchar label_str[ITEM_LABEL_LENGTH]; + gchar *label_ptr; + size_t label_len; + guint32 i, i_restart; + guint8 c_char; + size_t c_match = 0; + + /* dissection with an invisible proto tree? */ + ws_assert(fi); + + if (mdata->frame_matched) { + /* We already had a match; don't bother doing any more work. */ + return; + } + + /* Don't match invisible entries. */ + if (proto_item_is_hidden(node)) + return; + + /* was a free format label produced? */ + if (fi->rep) { + label_ptr = fi->rep->representation; + } else { + /* no, make a generic label */ + label_ptr = label_str; + proto_item_fill_label(fi, label_str); + } + + if (cf->regex) { + if (ws_regex_matches(cf->regex, label_ptr)) { + mdata->frame_matched = TRUE; + mdata->finfo = fi; + return; + } + } else if (cf->case_type) { + /* Case insensitive match */ + label_len = strlen(label_ptr); + i_restart = 0; + for (i = 0; i < label_len; i++) { + if (i_restart == 0 && c_match == 0 && (label_len - i < string_len)) + break; + c_char = label_ptr[i]; + c_char = g_ascii_toupper(c_char); + /* If c_match is non-zero, save candidate for retrying full match. */ + if (c_match > 0 && i_restart == 0 && c_char == string[0]) + i_restart = i; + if (c_char == string[c_match]) { + c_match++; + if (c_match == string_len) { + /* No need to look further; we have a match */ + mdata->frame_matched = TRUE; + mdata->finfo = fi; + return; + } + } else if (i_restart) { + i = i_restart; + c_match = 1; + i_restart = 0; + } else + c_match = 0; + } + } else if (strstr(label_ptr, string) != NULL) { + /* Case sensitive match */ + mdata->frame_matched = TRUE; + mdata->finfo = fi; + return; + } + + /* Recurse into the subtree, if it exists */ + if (node->first_child != NULL) + proto_tree_children_foreach(node, match_subtree_text, mdata); +} + +gboolean +cf_find_packet_summary_line(capture_file *cf, const char *string, + search_direction dir) +{ + match_data mdata; + + mdata.string = string; + mdata.string_len = strlen(string); + return find_packet(cf, match_summary_line, &mdata, dir); +} + +static match_result +match_summary_line(capture_file *cf, frame_data *fdata, + wtap_rec *rec, Buffer *buf, void *criterion) +{ + match_data *mdata = (match_data *)criterion; + const gchar *string = mdata->string; + size_t string_len = mdata->string_len; + epan_dissect_t edt; + const char *info_column; + size_t info_column_len; + match_result result = MR_NOTMATCHED; + gint colx; + guint32 i, i_restart; + guint8 c_char; + size_t c_match = 0; + + /* Load the frame's data. */ + if (!cf_read_record(cf, fdata, rec, buf)) { + /* Attempt to get the packet failed. */ + return MR_ERROR; + } + + /* Don't bother constructing the protocol tree */ + epan_dissect_init(&edt, cf->epan, FALSE, FALSE); + /* Get the column information */ + epan_dissect_run(&edt, cf->cd_t, rec, + frame_tvbuff_new_buffer(&cf->provider, fdata, buf), + fdata, &cf->cinfo); + + /* Find the Info column */ + for (colx = 0; colx < cf->cinfo.num_cols; colx++) { + if (cf->cinfo.columns[colx].fmt_matx[COL_INFO]) { + /* Found it. See if we match. */ + info_column = get_column_text(edt.pi.cinfo, colx); + info_column_len = strlen(info_column); + if (cf->regex) { + if (ws_regex_matches(cf->regex, info_column)) { + result = MR_MATCHED; + break; + } + } else if (cf->case_type) { + /* Case insensitive match */ + i_restart = 0; + for (i = 0; i < info_column_len; i++) { + if (i_restart == 0 && c_match == 0 && (info_column_len - i < string_len)) + break; + c_char = info_column[i]; + c_char = g_ascii_toupper(c_char); + /* If c_match is non-zero, save candidate for retrying full match. */ + if (c_match > 0 && i_restart == 0 && c_char == string[0]) + i_restart = i; + if (c_char == string[c_match]) { + c_match++; + if (c_match == string_len) { + result = MR_MATCHED; + break; + } + } else if (i_restart) { + i = i_restart; + c_match = 1; + i_restart = 0; + } else + c_match = 0; + } + } else if (strstr(info_column, string) != NULL) { + /* Case sensitive match */ + result = MR_MATCHED; + } + break; + } + } + epan_dissect_cleanup(&edt); + return result; +} + +typedef struct { + const guint8 *data; + size_t data_len; + ws_mempbrk_pattern *pattern; +} cbs_t; /* "Counted byte string" */ + + +/* + * The current match_* routines only support ASCII case insensitivity and don't + * convert UTF-8 inputs to UTF-16 for matching. The UTF-16 support just + * interleaves with \0 bytes, which works for 7 bit ASCII. + * + * We could modify them to use the GLib Unicode routines or the International + * Components for Unicode library but it's not apparent that we could do so + * without consuming a lot more CPU and memory or that searching would be + * significantly better. + * + * XXX: We could test the search string to see if it's all ASCII, and if not + * use Unicode aware routines for case insensitive searches or any UTF-16 + * search. + */ + +gboolean +cf_find_packet_data(capture_file *cf, const guint8 *string, size_t string_size, + search_direction dir) +{ + cbs_t info; + guint8 needles[3]; + ws_mempbrk_pattern pattern; + + info.data = string; + info.data_len = string_size; + + /* Regex, String or hex search? */ + if (cf->regex) { + /* Regular Expression search */ + return find_packet(cf, match_regex, NULL, dir); + } else if (cf->string) { + /* String search - what type of string? */ + if (cf->case_type) { + needles[0] = string[0]; + needles[1] = g_ascii_tolower(needles[0]); + needles[2] = '\0'; + ws_mempbrk_compile(&pattern, needles); + info.pattern = &pattern; + switch (cf->scs_type) { + + case SCS_NARROW_AND_WIDE: + return find_packet(cf, match_narrow_and_wide_case, &info, dir); + + case SCS_NARROW: + return find_packet(cf, match_narrow_case, &info, dir); + + case SCS_WIDE: + return find_packet(cf, match_wide_case, &info, dir); + + default: + ws_assert_not_reached(); + return FALSE; + } + + } else { + switch (cf->scs_type) { + + case SCS_NARROW_AND_WIDE: + return find_packet(cf, match_narrow_and_wide, &info, dir); + + case SCS_NARROW: + return find_packet(cf, match_narrow, &info, dir); + + case SCS_WIDE: + return find_packet(cf, match_wide, &info, dir); + + default: + ws_assert_not_reached(); + return FALSE; + } + } + } else + return find_packet(cf, match_binary, &info, dir); +} + +static match_result +match_narrow_and_wide(capture_file *cf, frame_data *fdata, + wtap_rec *rec, Buffer *buf, void *criterion) +{ + cbs_t *info = (cbs_t *)criterion; + const guint8 *ascii_text = info->data; + size_t textlen = info->data_len; + match_result result; + guint32 buf_len; + guint8 *pd, *buf_start, *buf_end; + guint32 i; + guint8 c_char; + size_t c_match = 0; + + /* Load the frame's data. */ + if (!cf_read_record(cf, fdata, rec, buf)) { + /* Attempt to get the packet failed. */ + return MR_ERROR; + } + + result = MR_NOTMATCHED; + buf_len = fdata->cap_len; + buf_start = ws_buffer_start_ptr(buf); + buf_end = buf_start + buf_len; + for (pd = buf_start; pd < buf_end; pd++) { + pd = (guint8 *)memchr(pd, ascii_text[0], buf_end - pd); + if (pd == NULL) break; + /* Try narrow match at this start location */ + c_match = 0; + for (i = 0; pd + i < buf_end; i++) { + c_char = pd[i]; + if (c_char == ascii_text[c_match]) { + c_match++; + if (c_match == textlen) { + result = MR_MATCHED; + cf->search_pos = i + (guint32)(pd - buf_start); + /* Save the position of the last character + for highlighting the field. */ + cf->search_len = i + 1; + goto done; + } + } else { + break; + } + } + + /* Now try wide match at the same start location. */ + c_match = 0; + for (i = 0; pd + i < buf_end; i++) { + c_char = pd[i]; + if (c_char == ascii_text[c_match]) { + c_match++; + if (c_match == textlen) { + result = MR_MATCHED; + cf->search_pos = i + (guint32)(pd - buf_start); + /* Save the position of the last character + for highlighting the field. */ + cf->search_len = i + 1; + goto done; + } + i++; + if (pd + i >= buf_end || pd[i] != '\0') break; + } else { + break; + } + } + } + +done: + return result; +} + +/* Case insensitive match */ +static match_result +match_narrow_and_wide_case(capture_file *cf, frame_data *fdata, + wtap_rec *rec, Buffer *buf, void *criterion) +{ + cbs_t *info = (cbs_t *)criterion; + const guint8 *ascii_text = info->data; + size_t textlen = info->data_len; + ws_mempbrk_pattern *pattern = info->pattern; + match_result result; + guint32 buf_len; + guint8 *pd, *buf_start, *buf_end; + guint32 i; + guint8 c_char; + size_t c_match = 0; + + /* Load the frame's data. */ + if (!cf_read_record(cf, fdata, rec, buf)) { + /* Attempt to get the packet failed. */ + return MR_ERROR; + } + + ws_assert(pattern != NULL); + + result = MR_NOTMATCHED; + buf_len = fdata->cap_len; + buf_start = ws_buffer_start_ptr(buf); + buf_end = buf_start + buf_len; + for (pd = buf_start; pd < buf_end; pd++) { + pd = (guint8 *)ws_mempbrk_exec(pd, buf_end - pd, pattern, &c_char); + if (pd == NULL) break; + /* Try narrow match at this start location */ + c_match = 0; + for (i = 0; pd + i < buf_end; i++) { + c_char = g_ascii_toupper(pd[i]); + if (c_char == ascii_text[c_match]) { + c_match++; + if (c_match == textlen) { + result = MR_MATCHED; + cf->search_pos = i + (guint32)(pd - buf_start); + /* Save the position of the last character + for highlighting the field. */ + cf->search_len = i + 1; + goto done; + } + } else { + break; + } + } + + /* Now try wide match at the same start location. */ + c_match = 0; + for (i = 0; pd + i < buf_end; i++) { + c_char = g_ascii_toupper(pd[i]); + if (c_char == ascii_text[c_match]) { + c_match++; + if (c_match == textlen) { + result = MR_MATCHED; + cf->search_pos = i + (guint32)(pd - buf_start); + /* Save the position of the last character + for highlighting the field. */ + cf->search_len = i + 1; + goto done; + } + i++; + if (pd + i >= buf_end || pd[i] != '\0') break; + } else { + break; + } + } + } + +done: + return result; +} + +static match_result +match_narrow(capture_file *cf, frame_data *fdata, + wtap_rec *rec, Buffer *buf, void *criterion) +{ + cbs_t *info = (cbs_t *)criterion; + const guint8 *ascii_text = info->data; + size_t textlen = info->data_len; + match_result result; + guint32 buf_len; + guint8 *pd, *buf_start, *buf_end; + guint32 i; + guint8 c_char; + size_t c_match = 0; + + /* Load the frame's data. */ + if (!cf_read_record(cf, fdata, rec, buf)) { + /* Attempt to get the packet failed. */ + return MR_ERROR; + } + + result = MR_NOTMATCHED; + buf_len = fdata->cap_len; + buf_start = ws_buffer_start_ptr(buf); + buf_end = buf_start + buf_len; + for (pd = buf_start; pd < buf_end; pd++) { + pd = (guint8 *)memchr(pd, ascii_text[0], buf_end - pd); + if (pd == NULL) break; + c_match = 0; + for (i = 0; pd + i < buf_end; i++) { + c_char = pd[i]; + if (c_char == ascii_text[c_match]) { + c_match++; + if (c_match == textlen) { + result = MR_MATCHED; + cf->search_pos = i + (guint32)(pd - buf_start); + /* Save the position of the last character + for highlighting the field. */ + cf->search_len = i + 1; + goto done; + } + } else { + break; + } + } + } + +done: + return result; +} + +/* Case insensitive match */ +static match_result +match_narrow_case(capture_file *cf, frame_data *fdata, + wtap_rec *rec, Buffer *buf, void *criterion) +{ + cbs_t *info = (cbs_t *)criterion; + const guint8 *ascii_text = info->data; + size_t textlen = info->data_len; + ws_mempbrk_pattern *pattern = info->pattern; + match_result result; + guint32 buf_len; + guint8 *pd, *buf_start, *buf_end; + guint32 i; + guint8 c_char; + size_t c_match = 0; + + /* Load the frame's data. */ + if (!cf_read_record(cf, fdata, rec, buf)) { + /* Attempt to get the packet failed. */ + return MR_ERROR; + } + + ws_assert(pattern != NULL); + + result = MR_NOTMATCHED; + buf_len = fdata->cap_len; + buf_start = ws_buffer_start_ptr(buf); + buf_end = buf_start + buf_len; + for (pd = buf_start; pd < buf_end; pd++) { + pd = (guint8 *)ws_mempbrk_exec(pd, buf_end - pd, pattern, &c_char); + if (pd == NULL) break; + c_match = 0; + for (i = 0; pd + i < buf_end; i++) { + c_char = g_ascii_toupper(pd[i]); + if (c_char == ascii_text[c_match]) { + c_match++; + if (c_match == textlen) { + result = MR_MATCHED; + cf->search_pos = i + (guint32)(pd - buf_start); + /* Save the position of the last character + for highlighting the field. */ + cf->search_len = i + 1; + goto done; + } + } else { + break; + } + } + } + +done: + return result; +} + +static match_result +match_wide(capture_file *cf, frame_data *fdata, + wtap_rec *rec, Buffer *buf, void *criterion) +{ + cbs_t *info = (cbs_t *)criterion; + const guint8 *ascii_text = info->data; + size_t textlen = info->data_len; + match_result result; + guint32 buf_len; + guint8 *pd, *buf_start, *buf_end; + guint32 i; + guint8 c_char; + size_t c_match = 0; + + /* Load the frame's data. */ + if (!cf_read_record(cf, fdata, rec, buf)) { + /* Attempt to get the packet failed. */ + return MR_ERROR; + } + + result = MR_NOTMATCHED; + buf_len = fdata->cap_len; + buf_start = ws_buffer_start_ptr(buf); + buf_end = buf_start + buf_len; + for (pd = buf_start; pd < buf_end; pd++) { + pd = (guint8 *)memchr(pd, ascii_text[0], buf_end - pd); + if (pd == NULL) break; + c_match = 0; + for (i = 0; pd + i < buf_end; i++) { + c_char = pd[i]; + if (c_char == ascii_text[c_match]) { + c_match++; + if (c_match == textlen) { + result = MR_MATCHED; + cf->search_pos = i + (guint32)(pd - buf_start); + /* Save the position of the last character + for highlighting the field. */ + cf->search_len = i + 1; + goto done; + } + i++; + if (pd + i >= buf_end || pd[i] != '\0') break; + } else { + break; + } + } + } + +done: + return result; +} + +/* Case insensitive match */ +static match_result +match_wide_case(capture_file *cf, frame_data *fdata, + wtap_rec *rec, Buffer *buf, void *criterion) +{ + cbs_t *info = (cbs_t *)criterion; + const guint8 *ascii_text = info->data; + size_t textlen = info->data_len; + ws_mempbrk_pattern *pattern = info->pattern; + match_result result; + guint32 buf_len; + guint8 *pd, *buf_start, *buf_end; + guint32 i; + guint8 c_char; + size_t c_match = 0; + + /* Load the frame's data. */ + if (!cf_read_record(cf, fdata, rec, buf)) { + /* Attempt to get the packet failed. */ + return MR_ERROR; + } + + ws_assert(pattern != NULL); + + result = MR_NOTMATCHED; + buf_len = fdata->cap_len; + buf_start = ws_buffer_start_ptr(buf); + buf_end = buf_start + buf_len; + for (pd = buf_start; pd < buf_end; pd++) { + pd = (guint8 *)ws_mempbrk_exec(pd, buf_end - pd, pattern, &c_char); + if (pd == NULL) break; + c_match = 0; + for (i = 0; pd + i < buf_end; i++) { + c_char = g_ascii_toupper(pd[i]); + if (c_char == ascii_text[c_match]) { + c_match++; + if (c_match == textlen) { + result = MR_MATCHED; + cf->search_pos = i + (guint32)(pd - buf_start); + /* Save the position of the last character + for highlighting the field. */ + cf->search_len = i + 1; + goto done; + } + i++; + if (pd + i >= buf_end || pd[i] != '\0') break; + } else { + break; + } + } + } + +done: + return result; +} + +static match_result +match_binary(capture_file *cf, frame_data *fdata, + wtap_rec *rec, Buffer *buf, void *criterion) +{ + cbs_t *info = (cbs_t *)criterion; + const guint8 *binary_data = info->data; + size_t datalen = info->data_len; + match_result result; + guint32 buf_len; + guint8 *pd, *buf_start, *buf_end; + guint32 i; + size_t c_match = 0; + + /* Load the frame's data. */ + if (!cf_read_record(cf, fdata, rec, buf)) { + /* Attempt to get the packet failed. */ + return MR_ERROR; + } + + result = MR_NOTMATCHED; + buf_len = fdata->cap_len; + buf_start = ws_buffer_start_ptr(buf); + buf_end = buf_start + buf_len; + /* Not clear if using memcmp() is faster. memmem() on systems that + * have it should be faster, though. + */ + for (pd = buf_start; pd < buf_end; pd++) { + pd = (guint8 *)memchr(pd, binary_data[0], buf_end - pd); + if (pd == NULL) break; + c_match = 0; + for (i = 0; pd + i < buf_end; i++) { + if (pd[i] == binary_data[c_match]) { + c_match++; + if (c_match == datalen) { + result = MR_MATCHED; + cf->search_pos = i + (guint32)(pd - buf_start); + /* Save the position of the last character + for highlighting the field. */ + cf->search_len = i + 1; + goto done; + } + } else { + break; + } + } + } + +done: + return result; +} + +static match_result +match_regex(capture_file *cf, frame_data *fdata, + wtap_rec *rec, Buffer *buf, void *criterion _U_) +{ + match_result result = MR_NOTMATCHED; + size_t result_pos[2] = {0, 0}; + + /* Load the frame's data. */ + if (!cf_read_record(cf, fdata, rec, buf)) { + /* Attempt to get the packet failed. */ + return MR_ERROR; + } + + if (ws_regex_matches_pos(cf->regex, + (const gchar *)ws_buffer_start_ptr(buf), + fdata->cap_len, + result_pos)) { + //TODO: Fix cast. + cf->search_pos = (guint32)(result_pos[1] - 1); /* last byte = end position - 1 */ + cf->search_len = (guint32)(result_pos[1] - result_pos[0]); + result = MR_MATCHED; + } + return result; +} + +gboolean +cf_find_packet_dfilter(capture_file *cf, dfilter_t *sfcode, + search_direction dir) +{ + return find_packet(cf, match_dfilter, sfcode, dir); +} + +gboolean +cf_find_packet_dfilter_string(capture_file *cf, const char *filter, + search_direction dir) +{ + dfilter_t *sfcode; + gboolean result; + + if (!dfilter_compile(filter, &sfcode, NULL)) { + /* + * XXX - this shouldn't happen, as the filter string is machine + * generated + */ + return FALSE; + } + if (sfcode == NULL) { + /* + * XXX - this shouldn't happen, as the filter string is machine + * generated. + */ + return FALSE; + } + result = find_packet(cf, match_dfilter, sfcode, dir); + dfilter_free(sfcode); + return result; +} + +static match_result +match_dfilter(capture_file *cf, frame_data *fdata, + wtap_rec *rec, Buffer *buf, void *criterion) +{ + dfilter_t *sfcode = (dfilter_t *)criterion; + epan_dissect_t edt; + match_result result; + + /* Load the frame's data. */ + if (!cf_read_record(cf, fdata, rec, buf)) { + /* Attempt to get the packet failed. */ + return MR_ERROR; + } + + epan_dissect_init(&edt, cf->epan, TRUE, FALSE); + epan_dissect_prime_with_dfilter(&edt, sfcode); + epan_dissect_run(&edt, cf->cd_t, rec, + frame_tvbuff_new_buffer(&cf->provider, fdata, buf), + fdata, NULL); + result = dfilter_apply_edt(sfcode, &edt) ? MR_MATCHED : MR_NOTMATCHED; + epan_dissect_cleanup(&edt); + return result; +} + +gboolean +cf_find_packet_marked(capture_file *cf, search_direction dir) +{ + return find_packet(cf, match_marked, NULL, dir); +} + +static match_result +match_marked(capture_file *cf _U_, frame_data *fdata, wtap_rec *rec _U_, + Buffer *buf _U_, void *criterion _U_) +{ + return fdata->marked ? MR_MATCHED : MR_NOTMATCHED; +} + +gboolean +cf_find_packet_time_reference(capture_file *cf, search_direction dir) +{ + return find_packet(cf, match_time_reference, NULL, dir); +} + +static match_result +match_time_reference(capture_file *cf _U_, frame_data *fdata, wtap_rec *rec _U_, + Buffer *buf _U_, void *criterion _U_) +{ + return fdata->ref_time ? MR_MATCHED : MR_NOTMATCHED; +} + +static gboolean +find_packet(capture_file *cf, ws_match_function match_function, + void *criterion, search_direction dir) +{ + frame_data *start_fd; + guint32 framenum; + guint32 prev_framenum; + frame_data *fdata; + wtap_rec rec; + Buffer buf; + frame_data *new_fd = NULL; + progdlg_t *progbar = NULL; + GTimer *prog_timer = g_timer_new(); + int count; + gboolean succeeded; + float progbar_val; + gchar status_str[100]; + match_result result; + + wtap_rec_init(&rec); + ws_buffer_init(&buf, 1514); + + start_fd = cf->current_frame; + if (start_fd != NULL) { + prev_framenum = start_fd->num; + } else { + prev_framenum = 0; /* No start packet selected. */ + } + + /* Iterate through the list of packets, starting at the packet we've + picked, calling a routine to run the filter on the packet, see if + it matches, and stop if so. */ + count = 0; + framenum = prev_framenum; + + g_timer_start(prog_timer); + /* Progress so far. */ + progbar_val = 0.0f; + + cf->stop_flag = FALSE; + + for (;;) { + /* Create the progress bar if necessary. + We check on every iteration of the loop, so that it takes no + longer than the standard time to create it (otherwise, for a + large file, we might take considerably longer than that standard + time in order to get to the next progress bar step). */ + if (progbar == NULL) + progbar = delayed_create_progress_dlg(cf->window, NULL, NULL, + FALSE, &cf->stop_flag, progbar_val); + + /* + * Update the progress bar, but do it only after PROGBAR_UPDATE_INTERVAL + * has elapsed. Calling update_progress_dlg and packets_bar_update will + * likely trigger UI paint events, which might take a while depending on + * the platform and display. Reset our timer *after* painting. + */ + if (g_timer_elapsed(prog_timer, NULL) > PROGBAR_UPDATE_INTERVAL) { + /* let's not divide by zero. I should never be started + * with count == 0, so let's assert that + */ + ws_assert(cf->count > 0); + + progbar_val = (gfloat) count / cf->count; + + snprintf(status_str, sizeof(status_str), + "%4u of %u packets", count, cf->count); + update_progress_dlg(progbar, progbar_val, status_str); + + g_timer_start(prog_timer); + } + + if (cf->stop_flag) { + /* Well, the user decided to abort the search. Go back to the + frame where we started. */ + new_fd = start_fd; + break; + } + + /* Go past the current frame. */ + if (dir == SD_BACKWARD) { + /* Go on to the previous frame. */ + if (framenum <= 1) { + /* + * XXX - other apps have a bit more of a detailed message + * for this, and instead of offering "OK" and "Cancel", + * they offer things such as "Continue" and "Cancel"; + * we need an API for popping up alert boxes with + * {Verb} and "Cancel". + */ + + if (prefs.gui_find_wrap) { + statusbar_push_temporary_msg("Search reached the beginning. Continuing at end."); + framenum = cf->count; /* wrap around */ + } else { + statusbar_push_temporary_msg("Search reached the beginning."); + framenum = prev_framenum; /* stay on previous packet */ + } + } else + framenum--; + } else { + /* Go on to the next frame. */ + if (framenum == cf->count) { + if (prefs.gui_find_wrap) { + statusbar_push_temporary_msg("Search reached the end. Continuing at beginning."); + framenum = 1; /* wrap around */ + } else { + statusbar_push_temporary_msg("Search reached the end."); + framenum = prev_framenum; /* stay on previous packet */ + } + } else + framenum++; + } + + fdata = frame_data_sequence_find(cf->provider.frames, framenum); + count++; + + /* Is this packet in the display? */ + if (fdata && fdata->passed_dfilter) { + /* Yes. Does it match the search criterion? */ + result = (*match_function)(cf, fdata, &rec, &buf, criterion); + if (result == MR_ERROR) { + /* Error; our caller has reported the error. Go back to the frame + where we started. */ + new_fd = start_fd; + break; + } else if (result == MR_MATCHED) { + /* Yes. Go to the new frame. */ + new_fd = fdata; + break; + } + wtap_rec_reset(&rec); + } + + if (fdata == start_fd) { + /* We're back to the frame we were on originally, and that frame + doesn't match the search filter. The search failed. */ + break; + } + } + + /* We're done scanning the packets; destroy the progress bar if it + was created. */ + if (progbar != NULL) + destroy_progress_dlg(progbar); + g_timer_destroy(prog_timer); + + if (new_fd != NULL) { + /* We found a frame that's displayed and that matches. + Try to find and select the packet summary list row for that frame. */ + gboolean found_row; + + cf->search_in_progress = TRUE; + found_row = packet_list_select_row_from_data(new_fd); + cf->search_in_progress = FALSE; + cf->search_pos = 0; /* Reset the position */ + cf->search_len = 0; /* Reset length */ + if (!found_row) { + /* We didn't find a row corresponding to this frame. + This means that the frame isn't being displayed currently, + so we can't select it. */ + simple_message_box(ESD_TYPE_INFO, NULL, + "The capture file is probably not fully dissected.", + "End of capture exceeded."); + succeeded = FALSE; /* The search succeeded but we didn't find the row */ + } else + succeeded = TRUE; /* The search succeeded and we found the row */ + } else + succeeded = FALSE; /* The search failed */ + wtap_rec_cleanup(&rec); + ws_buffer_free(&buf); + return succeeded; +} + +gboolean +cf_goto_frame(capture_file *cf, guint fnumber) +{ + frame_data *fdata; + + if (cf == NULL || cf->provider.frames == NULL) { + /* we don't have a loaded capture file - fix for bugs 11810 & 11989 */ + statusbar_push_temporary_msg("There is no file loaded"); + return FALSE; /* we failed to go to that packet */ + } + + fdata = frame_data_sequence_find(cf->provider.frames, fnumber); + + if (fdata == NULL) { + /* we didn't find a packet with that packet number */ + statusbar_push_temporary_msg("There is no packet number %u.", fnumber); + return FALSE; /* we failed to go to that packet */ + } + if (!fdata->passed_dfilter) { + /* that packet currently isn't displayed */ + /* XXX - add it to the set of displayed packets? */ + statusbar_push_temporary_msg("Packet number %u isn't displayed.", fnumber); + return FALSE; /* we failed to go to that packet */ + } + + if (!packet_list_select_row_from_data(fdata)) { + /* We didn't find a row corresponding to this frame. + This means that the frame isn't being displayed currently, + so we can't select it. */ + simple_message_box(ESD_TYPE_INFO, NULL, + "The capture file is probably not fully dissected.", + "End of capture exceeded."); + return FALSE; + } + return TRUE; /* we got to that packet */ +} + +/* + * Go to frame specified by currently selected protocol tree item. + */ +gboolean +cf_goto_framenum(capture_file *cf) +{ + header_field_info *hfinfo; + guint32 framenum; + + if (cf->finfo_selected) { + hfinfo = cf->finfo_selected->hfinfo; + ws_assert(hfinfo); + if (hfinfo->type == FT_FRAMENUM) { + framenum = fvalue_get_uinteger(cf->finfo_selected->value); + if (framenum != 0) + return cf_goto_frame(cf, framenum); + } + } + + return FALSE; +} + +/* Select the packet on a given row. */ +void +cf_select_packet(capture_file *cf, frame_data *fdata) +{ + epan_dissect_t *old_edt; + + /* check the frame data struct pointer for this frame */ + if (fdata == NULL) { + return; + } + + /* Get the data in that frame. */ + if (!cf_read_record(cf, fdata, &cf->rec, &cf->buf)) { + return; + } + + /* Record that this frame is the current frame. */ + cf->current_frame = fdata; + + /* + * The change to defer freeing the current epan_dissect_t was in + * commit a2bb94c3b33d53f42534aceb7cc67aab1d1fb1f9; to quote + * that commit's comment: + * + * Clear GtkTreeStore before freeing edt + * + * When building current data for packet details treeview we store two + * things. + * - Generated string with item label + * - Pointer to node field_info structure + * + * After epan_dissect_{free, cleanup} pointer to field_info node is no + * longer valid so we should clear GtkTreeStore before freeing. + * + * XXX - we're no longer using GTK+; is there a way to ensure that + * *nothing* refers to any of the current frame information before + * we replace it? + */ + old_edt = cf->edt; + /* Create the logical protocol tree. */ + /* We don't need the columns here. */ + cf->edt = epan_dissect_new(cf->epan, TRUE, TRUE); + + tap_build_interesting(cf->edt); + epan_dissect_run(cf->edt, cf->cd_t, &cf->rec, + frame_tvbuff_new_buffer(&cf->provider, cf->current_frame, &cf->buf), + cf->current_frame, NULL); + + if (old_edt != NULL) + epan_dissect_free(old_edt); +} + +/* Unselect the selected packet, if any. */ +void +cf_unselect_packet(capture_file *cf) +{ + epan_dissect_t *old_edt = cf->edt; + + /* + * See the comment in cf_select_packet() about deferring the freeing + * of the old cf->edt. + */ + cf->edt = NULL; + + /* No packet is selected. */ + cf->current_frame = NULL; + + /* Destroy the epan_dissect_t for the unselected packet. */ + if (old_edt != NULL) + epan_dissect_free(old_edt); +} + +/* + * Mark a particular frame. + */ +void +cf_mark_frame(capture_file *cf, frame_data *frame) +{ + if (! frame->marked) { + frame->marked = TRUE; + if (cf->count > cf->marked_count) + cf->marked_count++; + } +} + +/* + * Unmark a particular frame. + */ +void +cf_unmark_frame(capture_file *cf, frame_data *frame) +{ + if (frame->marked) { + frame->marked = FALSE; + if (cf->marked_count > 0) + cf->marked_count--; + } +} + +/* + * Ignore a particular frame. + */ +void +cf_ignore_frame(capture_file *cf, frame_data *frame) +{ + if (! frame->ignored) { + frame->ignored = TRUE; + if (cf->count > cf->ignored_count) + cf->ignored_count++; + } +} + +/* + * Un-ignore a particular frame. + */ +void +cf_unignore_frame(capture_file *cf, frame_data *frame) +{ + if (frame->ignored) { + frame->ignored = FALSE; + if (cf->ignored_count > 0) + cf->ignored_count--; + } +} + +/* + * Modify the section comment. + */ +void +cf_update_section_comment(capture_file *cf, gchar *comment) +{ + wtap_block_t shb_inf; + gchar *shb_comment; + + /* Get the first SHB. */ + /* XXX - support multiple SHBs */ + shb_inf = wtap_file_get_shb(cf->provider.wth, 0); + + /* Get the first comment from the SHB. */ + /* XXX - support multiple comments */ + if (wtap_block_get_nth_string_option_value(shb_inf, OPT_COMMENT, 0, &shb_comment) != WTAP_OPTTYPE_SUCCESS) { + /* There's no comment - add one. */ + wtap_block_add_string_option(shb_inf, OPT_COMMENT, comment, strlen(comment)); + } else { + /* See if the comment has changed or not */ + if (strcmp(shb_comment, comment) == 0) { + g_free(comment); + return; + } + + /* The comment has changed, let's update it */ + wtap_block_set_nth_string_option_value(shb_inf, OPT_COMMENT, 0, comment, strlen(comment)); + } + /* Mark the file as having unsaved changes */ + cf->unsaved_changes = TRUE; +} + +/* + * Get the packet block for a packet (record). + * If the block has been edited, it returns the result of the edit, + * otherwise it returns the block from the file. + * NB. Caller must wtap_block_unref() the result when done. + */ +wtap_block_t +cf_get_packet_block(capture_file *cf, const frame_data *fd) +{ + /* If this block has been modified, fetch the modified version */ + if (fd->has_modified_block) + return wtap_block_ref(cap_file_provider_get_modified_block(&cf->provider, fd)); + else { + wtap_rec rec; /* Record metadata */ + Buffer buf; /* Record data */ + wtap_block_t block; + + /* fetch record block */ + wtap_rec_init(&rec); + ws_buffer_init(&buf, 1514); + + if (!cf_read_record(cf, fd, &rec, &buf)) + { /* XXX, what we can do here? */ } + + /* rec.block is owned by the record, steal it before it is gone. */ + block = wtap_block_ref(rec.block); + + wtap_rec_cleanup(&rec); + ws_buffer_free(&buf); + return block; + } +} + +/* + * Update(replace) the block on a capture from a frame + */ +gboolean +cf_set_modified_block(capture_file *cf, frame_data *fd, const wtap_block_t new_block) +{ + wtap_block_t pkt_block = cf_get_packet_block(cf, fd); + + /* It's possible to further modify the modified block "in place" by doing + * a call to cf_get_packet_block() that returns an already created modified + * block, modifying that, and calling this function. + * If the caller did that, then the block pointers will be equal. + */ + if (pkt_block == new_block) { + /* No need to save anything here, the caller changes went right + * onto the block. + * Unfortunately we don't have a way to know how many comments were + * in the block before the caller modified it, so tell the caller + * it is its responsibility to update the comment count. + */ + return FALSE; + } + else { + if (pkt_block) + cf->packet_comment_count -= wtap_block_count_option(pkt_block, OPT_COMMENT); + + if (new_block) + cf->packet_comment_count += wtap_block_count_option(new_block, OPT_COMMENT); + + cap_file_provider_set_modified_block(&cf->provider, fd, new_block); + + expert_update_comment_count(cf->packet_comment_count); + } + + /* Either way, we have unsaved changes. */ + wtap_block_unref(pkt_block); + cf->unsaved_changes = TRUE; + return TRUE; +} + +/* + * What types of comments does this capture file have? + */ +guint32 +cf_comment_types(capture_file *cf) +{ + guint32 comment_types = 0; + + /* + * Does this file have any sections with at least one comment? + */ + for (guint section_number = 0; + section_number < wtap_file_get_num_shbs(cf->provider.wth); + section_number++) { + wtap_block_t shb_inf; + char *shb_comment; + + shb_inf = wtap_file_get_shb(cf->provider.wth, section_number); + + /* Try to get the first comment from that SHB. */ + if (wtap_block_get_nth_string_option_value(shb_inf, OPT_COMMENT, 0, + &shb_comment) == WTAP_OPTTYPE_SUCCESS) { + /* We succeeded, so this file has at least one section comment. */ + comment_types |= WTAP_COMMENT_PER_SECTION; + + /* We don't need to search any more. */ + break; + } + } + if (cf->packet_comment_count != 0) + comment_types |= WTAP_COMMENT_PER_PACKET; + return comment_types; +} + +/* + * Add a resolved address to this file's list of resolved addresses. + */ +gboolean +cf_add_ip_name_from_string(capture_file *cf, const char *addr, const char *name) +{ + /* + * XXX - support multiple resolved address lists, and add to the one + * attached to this file? + */ + if (!add_ip_name_from_string(addr, name)) + return FALSE; + + /* OK, we have unsaved changes. */ + cf->unsaved_changes = TRUE; + return TRUE; +} + +typedef struct { + wtap_dumper *pdh; + const char *fname; + int file_type; + gboolean export; +} save_callback_args_t; + +/* + * Save a capture to a file, in a particular format, saving either + * all packets, all currently-displayed packets, or all marked packets. + * + * Returns TRUE if it succeeds, FALSE otherwise; if it fails, it pops + * up a message box for the failure. + */ +static gboolean +save_record(capture_file *cf, frame_data *fdata, wtap_rec *rec, + Buffer *buf, void *argsp) +{ + save_callback_args_t *args = (save_callback_args_t *)argsp; + wtap_rec new_rec; + int err; + gchar *err_info; + wtap_block_t pkt_block; + + /* Copy the record information from what was read in from the file. */ + new_rec = *rec; + + /* Make changes based on anything that the user has done but that + hasn't been saved yet. */ + if (fdata->has_modified_block) + pkt_block = cap_file_provider_get_modified_block(&cf->provider, fdata); + else + pkt_block = rec->block; + new_rec.block = pkt_block; + new_rec.block_was_modified = fdata->has_modified_block ? TRUE : FALSE; + + if (!nstime_is_zero(&fdata->shift_offset)) { + if (new_rec.presence_flags & WTAP_HAS_TS) { + nstime_add(&new_rec.ts, &fdata->shift_offset); + } + } + + /* and save the packet */ + if (!wtap_dump(args->pdh, &new_rec, ws_buffer_start_ptr(buf), &err, &err_info)) { + cfile_write_failure_alert_box(NULL, args->fname, err, err_info, fdata->num, + args->file_type); + return FALSE; + } + + /* If we are saving (i.e., replacing the current file with the one we're + * writing), then update the frame data to clear the shift offset. + * This keeps us from having to re-read the entire file. + * We could do this in rescan_file(), but + * 1) Ideally we shouldn't have to call rescan_file if all we're doing + * is changing the timestamps, since that shouldn't change the offsets. + * 2) The long term goal is to try to do the offset adjustment here + * instead of using rescan_file, which should be faster (#1257). + * + * If we're exporting to a different file, then don't do that. + */ + if (!args->export && new_rec.presence_flags & WTAP_HAS_TS) { + nstime_set_zero(&fdata->shift_offset); + } + + return TRUE; +} + +/* + * Can this capture file be written out in any format using Wiretap + * rather than by copying the raw data? + */ +gboolean +cf_can_write_with_wiretap(capture_file *cf) +{ + /* We don't care whether we support the comments in this file or not; + if we can't, we'll offer the user the option of discarding the + comments. */ + return wtap_dump_can_write(cf->linktypes, 0); +} + +/* + * Should we let the user do a save? + * + * We should if: + * + * the file has unsaved changes, and we can save it in some + * format through Wiretap + * + * or + * + * the file is a temporary file and has no unsaved changes (so + * that "saving" it just means copying it). + * + * XXX - we shouldn't allow files to be edited if they can't be saved, + * so cf->unsaved_changes should be true only if the file can be saved. + * + * We don't care whether we support the comments in this file or not; + * if we can't, we'll offer the user the option of discarding the + * comments. + */ +gboolean +cf_can_save(capture_file *cf) +{ + if (cf->unsaved_changes && wtap_dump_can_write(cf->linktypes, 0)) { + /* Saved changes, and we can write it out with Wiretap. */ + return TRUE; + } + + if (cf->is_tempfile && !cf->unsaved_changes) { + /* + * Temporary file with no unsaved changes, so we can just do a + * raw binary copy. + */ + return TRUE; + } + + /* Nothing to save. */ + return FALSE; +} + +/* + * Should we let the user do a "save as"? + * + * That's true if: + * + * we can save it in some format through Wiretap + * + * or + * + * the file is a temporary file and has no unsaved changes (so + * that "saving" it just means copying it). + * + * XXX - we shouldn't allow files to be edited if they can't be saved, + * so cf->unsaved_changes should be true only if the file can be saved. + * + * We don't care whether we support the comments in this file or not; + * if we can't, we'll offer the user the option of discarding the + * comments. + */ +gboolean +cf_can_save_as(capture_file *cf) +{ + if (wtap_dump_can_write(cf->linktypes, 0)) { + /* We can write it out with Wiretap. */ + return TRUE; + } + + if (cf->is_tempfile && !cf->unsaved_changes) { + /* + * Temporary file with no unsaved changes, so we can just do a + * raw binary copy. + */ + return TRUE; + } + + /* Nothing to save. */ + return FALSE; +} + +/* + * Does this file have unsaved data? + */ +gboolean +cf_has_unsaved_data(capture_file *cf) +{ + /* + * If this is a temporary file, or a file with unsaved changes, it + * has unsaved data. + */ + return (cf->is_tempfile && cf->count>0) || cf->unsaved_changes; +} + +/* + * Quick scan to find packet offsets. + */ +static cf_read_status_t +rescan_file(capture_file *cf, const char *fname, gboolean is_tempfile) +{ + wtap_rec rec; + Buffer buf; + int err; + gchar *err_info; + gchar *name_ptr; + gint64 data_offset; + progdlg_t *progbar = NULL; + GTimer *prog_timer = g_timer_new(); + gint64 size; + float progbar_val; + gint64 start_time; + gchar status_str[100]; + guint32 framenum; + frame_data *fdata; + + /* Close the old handle. */ + wtap_close(cf->provider.wth); + + /* Open the new file. */ + /* XXX: this will go through all open_routines for a matching one. But right + now rescan_file() is only used when a file is being saved to a different + format than the original, and the user is not given a choice of which + reader to use (only which format to save it in), so doing this makes + sense for now. (XXX: Now it is also used when saving a changed file, + e.g. comments or time-shifted frames.) */ + cf->provider.wth = wtap_open_offline(fname, WTAP_TYPE_AUTO, &err, &err_info, TRUE); + if (cf->provider.wth == NULL) { + cfile_open_failure_alert_box(fname, err, err_info); + return CF_READ_ERROR; + } + + /* We're scanning a file whose contents should be the same as what + we had before, so we don't discard dissection state etc.. */ + cf->f_datalen = 0; + + /* Set the file name because we need it to set the follow stream filter. + XXX - is that still true? We need it for other reasons, though, + in any case. */ + if (cf->filename != NULL) { + g_free(cf->filename); + } + cf->filename = g_strdup(fname); + + /* Indicate whether it's a permanent or temporary file. */ + cf->is_tempfile = is_tempfile; + + /* No user changes yet. */ + cf->unsaved_changes = FALSE; + + cf->cd_t = wtap_file_type_subtype(cf->provider.wth); + if (cf->linktypes != NULL) { + g_array_free(cf->linktypes, TRUE); + } + cf->linktypes = g_array_sized_new(FALSE, FALSE, (guint) sizeof(int), 1); + + cf->snap = wtap_snapshot_length(cf->provider.wth); + + name_ptr = g_filename_display_basename(cf->filename); + + cf_callback_invoke(cf_cb_file_rescan_started, cf); + + /* Record the file's compression type. + XXX - do we know this at open time? */ + cf->compression_type = wtap_get_compression_type(cf->provider.wth); + + /* Find the size of the file. */ + size = wtap_file_size(cf->provider.wth, NULL); + + g_timer_start(prog_timer); + + cf->stop_flag = FALSE; + start_time = g_get_monotonic_time(); + + framenum = 0; + wtap_rec_init(&rec); + ws_buffer_init(&buf, 1514); + while ((wtap_read(cf->provider.wth, &rec, &buf, &err, &err_info, + &data_offset))) { + framenum++; + fdata = frame_data_sequence_find(cf->provider.frames, framenum); + if (G_LIKELY(fdata != NULL)) { + fdata->file_off = data_offset; + } + if (size >= 0) { + cf->f_datalen = wtap_read_so_far(cf->provider.wth); + + /* Create the progress bar if necessary. */ + if (progress_is_slow(progbar, prog_timer, size, cf->f_datalen)) { + progbar_val = calc_progbar_val(cf, size, cf->f_datalen, status_str, sizeof(status_str)); + progbar = delayed_create_progress_dlg(cf->window, NULL, NULL, + TRUE, &cf->stop_flag, progbar_val); + } + + /* + * Update the progress bar, but do it only after PROGBAR_UPDATE_INTERVAL + * has elapsed. Calling update_progress_dlg and packets_bar_update will + * likely trigger UI paint events, which might take a while depending on + * the platform and display. Reset our timer *after* painting. + */ + if (progbar && g_timer_elapsed(prog_timer, NULL) > PROGBAR_UPDATE_INTERVAL) { + progbar_val = calc_progbar_val(cf, size, cf->f_datalen, status_str, sizeof(status_str)); + /* update the packet bar content on the first run or frequently on very large files */ + update_progress_dlg(progbar, progbar_val, status_str); + compute_elapsed(cf, start_time); + packets_bar_update(); + g_timer_start(prog_timer); + } + } + + if (cf->stop_flag) { + /* Well, the user decided to abort the rescan. Sadly, as this + isn't a reread, recovering is difficult, so we'll just + close the current capture. */ + break; + } + + /* Add this packet's link-layer encapsulation type to cf->linktypes, if + it's not already there. + XXX - yes, this is O(N), so if every packet had a different + link-layer encapsulation type, it'd be O(N^2) to read the file, but + there are probably going to be a small number of encapsulation types + in a file. */ + if (rec.rec_type == REC_TYPE_PACKET) { + cf_add_encapsulation_type(cf, rec.rec_header.packet_header.pkt_encap); + } + wtap_rec_reset(&rec); + } + wtap_rec_cleanup(&rec); + ws_buffer_free(&buf); + + /* Free the display name */ + g_free(name_ptr); + + /* We're done reading the file; destroy the progress bar if it was created. */ + if (progbar != NULL) + destroy_progress_dlg(progbar); + g_timer_destroy(prog_timer); + + /* We're done reading sequentially through the file. */ + cf->state = FILE_READ_DONE; + + /* Close the sequential I/O side, to free up memory it requires. */ + wtap_sequential_close(cf->provider.wth); + + /* compute the time it took to load the file */ + compute_elapsed(cf, start_time); + + /* Set the file encapsulation type now; we don't know what it is until + we've looked at all the packets, as we don't know until then whether + there's more than one type (and thus whether it's + WTAP_ENCAP_PER_PACKET). */ + cf->lnk_t = wtap_file_encap(cf->provider.wth); + + cf_callback_invoke(cf_cb_file_rescan_finished, cf); + + if (cf->stop_flag) { + /* Our caller will give up at this point. */ + return CF_READ_ABORTED; + } + + if (err != 0) { + /* Put up a message box noting that the read failed somewhere along + the line. Don't throw out the stuff we managed to read, though, + if any. */ + cfile_read_failure_alert_box(NULL, err, err_info); + return CF_READ_ERROR; + } else + return CF_READ_OK; +} + +cf_write_status_t +cf_save_records(capture_file *cf, const char *fname, guint save_format, + wtap_compression_type compression_type, + gboolean discard_comments, gboolean dont_reopen) +{ + gchar *err_info; + gchar *fname_new = NULL; + wtap_dumper *pdh; + frame_data *fdata; + addrinfo_lists_t *addr_lists; + guint framenum; + int err; +#ifdef _WIN32 + gchar *display_basename; +#endif + enum { + SAVE_WITH_MOVE, + SAVE_WITH_COPY, + SAVE_WITH_WTAP + } how_to_save; + save_callback_args_t callback_args; + callback_args.export = FALSE; + gboolean needs_reload = FALSE; + + /* XXX caller should avoid saving the file while a read is pending + * (e.g. by delaying the save action) */ + if (cf->read_lock) { + ws_warning("cf_save_records(\"%s\") while the file is being read, potential crash ahead", fname); + } + + cf_callback_invoke(cf_cb_file_save_started, (gpointer)fname); + + addr_lists = get_addrinfo_list(); + + if (save_format == cf->cd_t && compression_type == cf->compression_type + && !discard_comments && !cf->unsaved_changes + && (wtap_addrinfo_list_empty(addr_lists) || wtap_file_type_subtype_supports_block(save_format, WTAP_BLOCK_NAME_RESOLUTION) == BLOCK_NOT_SUPPORTED)) { + /* We're saving in the format it's already in, and we're not discarding + comments, and there are no changes we have in memory that aren't saved + to the file, and we have no name resolution information to write or + the file format we're saving in doesn't support writing name + resolution information, so we can just move or copy the raw data. */ + + if (cf->is_tempfile) { + /* The file being saved is a temporary file from a live + capture, so it doesn't need to stay around under that name; + first, try renaming the capture buffer file to the new name. + This acts as a "safe save", in that, if the file already + exists, the existing file will be removed only if the rename + succeeds. + + Sadly, on Windows, as we have the current capture file + open, even MoveFileEx() with MOVEFILE_REPLACE_EXISTING + (to cause the rename to remove an existing target), as + done by ws_stdio_rename() (ws_rename() is #defined to + be ws_stdio_rename() on Windows) will fail. + + According to the MSDN documentation for CreateFile(), if, + when we open a capture file, we were to directly do a CreateFile(), + opening with FILE_SHARE_DELETE|FILE_SHARE_READ, and then + convert it to a file descriptor with _open_osfhandle(), + that would allow the file to be renamed out from under us. + + However, that doesn't work in practice. Perhaps the problem + is that the process doing the rename is the process that + has the file open. */ +#ifndef _WIN32 + if (ws_rename(cf->filename, fname) == 0) { + /* That succeeded - there's no need to copy the source file. */ + how_to_save = SAVE_WITH_MOVE; + } else { + if (errno == EXDEV) { + /* They're on different file systems, so we have to copy the + file. */ + how_to_save = SAVE_WITH_COPY; + } else { + /* The rename failed, but not because they're on different + file systems - put up an error message. (Or should we + just punt and try to copy? The only reason why I'd + expect the rename to fail and the copy to succeed would + be if we didn't have permission to remove the file from + the temporary directory, and that might be fixable - but + is it worth requiring the user to go off and fix it?) */ + cf_rename_failure_alert_box(fname, errno); + goto fail; + } + } +#else + /* Windows - copy the file to its new location. */ + how_to_save = SAVE_WITH_COPY; +#endif + } else { + /* It's a permanent file, so we should copy it, and not remove the + original. */ + how_to_save = SAVE_WITH_COPY; + } + + if (how_to_save == SAVE_WITH_COPY) { + /* Copy the file, if we haven't moved it. If we're overwriting + an existing file, we do it with a "safe save", by writing + to a new file and, if the write succeeds, renaming the + new file on top of the old file. */ + if (file_exists(fname)) { + fname_new = ws_strdup_printf("%s~", fname); + if (!copy_file_binary_mode(cf->filename, fname_new)) + goto fail; + } else { + if (!copy_file_binary_mode(cf->filename, fname)) + goto fail; + } + } + } else { + /* Either we're saving in a different format or we're saving changes, + such as added, modified, or removed comments, that haven't yet + been written to the underlying file; we can't do that by copying + or moving the capture file, we have to do it by writing the packets + out in Wiretap. */ + + wtap_dump_params params; + int encap; + + how_to_save = SAVE_WITH_WTAP; + wtap_dump_params_init(¶ms, cf->provider.wth); + + /* Determine what file encapsulation type we should use. */ + encap = wtap_dump_required_file_encap_type(cf->linktypes); + params.encap = encap; + + /* Use the snaplen from cf (XXX - does wtap_dump_params_init handle that?) */ + params.snaplen = cf->snap; + + if (file_exists(fname)) { + /* We're overwriting an existing file; write out to a new file, + and, if that succeeds, rename the new file on top of the + old file. That makes this a "safe save", so that we don't + lose the old file if we have a problem writing out the new + file. (If the existing file is the current capture file, + we *HAVE* to do that, otherwise we're overwriting the file + from which we're reading the packets that we're writing!) */ + fname_new = ws_strdup_printf("%s~", fname); + pdh = wtap_dump_open(fname_new, save_format, compression_type, ¶ms, + &err, &err_info); + } else { + pdh = wtap_dump_open(fname, save_format, compression_type, ¶ms, + &err, &err_info); + } + /* XXX idb_inf is documented to be used until wtap_dump_close. */ + g_free(params.idb_inf); + params.idb_inf = NULL; + + if (pdh == NULL) { + cfile_dump_open_failure_alert_box(fname, err, err_info, save_format); + goto fail; + } + + /* Add address resolution */ + wtap_dump_set_addrinfo_list(pdh, addr_lists); + + /* Iterate through the list of packets, processing all the packets. */ + callback_args.pdh = pdh; + callback_args.fname = fname; + callback_args.file_type = save_format; + switch (process_specified_records(cf, NULL, "Saving", "packets", + TRUE, save_record, &callback_args, TRUE)) { + + case PSP_FINISHED: + /* Completed successfully. */ + break; + + case PSP_STOPPED: + /* The user decided to abort the saving. + If we're writing to a temporary file, remove it. + XXX - should we do so even if we're not writing to a + temporary file? */ + wtap_dump_close(pdh, NULL, &err, &err_info); + if (fname_new != NULL) + ws_unlink(fname_new); + cf_callback_invoke(cf_cb_file_save_stopped, NULL); + wtap_dump_params_cleanup(¶ms); + return CF_WRITE_ABORTED; + + case PSP_FAILED: + /* Error while saving. + If we're writing to a temporary file, remove it. */ + if (fname_new != NULL) + ws_unlink(fname_new); + wtap_dump_close(pdh, NULL, &err, &err_info); + wtap_dump_params_cleanup(¶ms); + goto fail; + } + + if (!wtap_dump_close(pdh, &needs_reload, &err, &err_info)) { + cfile_close_failure_alert_box(fname, err, err_info); + wtap_dump_params_cleanup(¶ms); + goto fail; + } + + wtap_dump_params_cleanup(¶ms); + } + + if (fname_new != NULL) { + /* We wrote out to fname_new, and should rename it on top of + fname. fname_new is now closed, so that should be possible even + on Windows. However, on Windows, we first need to close whatever + file descriptors we have open for fname. */ +#ifdef _WIN32 + wtap_fdclose(cf->provider.wth); +#endif + /* Now do the rename. */ + if (ws_rename(fname_new, fname) == -1) { + /* Well, the rename failed. */ + cf_rename_failure_alert_box(fname, errno); +#ifdef _WIN32 + /* Attempt to reopen the random file descriptor using the + current file's filename. (At this point, the sequential + file descriptor is closed.) */ + if (!wtap_fdreopen(cf->provider.wth, cf->filename, &err)) { + /* Oh, well, we're screwed. */ + display_basename = g_filename_display_basename(cf->filename); + simple_error_message_box( + file_open_error_message(err, FALSE), display_basename); + g_free(display_basename); + } +#endif + goto fail; + } + g_free(fname_new); + } + + /* If this was a temporary file, and we didn't do the save by doing + a move, so the tempoary file is still around under its old name, + remove it. */ + if (cf->is_tempfile && how_to_save != SAVE_WITH_MOVE) { + /* If this fails, there's not much we can do, so just ignore errors. */ + ws_unlink(cf->filename); + } + + cf_callback_invoke(cf_cb_file_save_finished, NULL); + cf->unsaved_changes = FALSE; + + if (!dont_reopen) { + switch (how_to_save) { + + case SAVE_WITH_MOVE: + /* We just moved the file, so the wtap structure refers to the + new file, and all the information other than the filename + and the "is temporary" status applies to the new file; just + update that. */ + g_free(cf->filename); + cf->filename = g_strdup(fname); + cf->is_tempfile = FALSE; + cf_callback_invoke(cf_cb_file_fast_save_finished, cf); + break; + + case SAVE_WITH_COPY: + /* We just copied the file, so all the information other than + the file descriptors, the filename, and the "is temporary" + status applies to the new file; just update that. */ + wtap_fdclose(cf->provider.wth); + /* Attempt to reopen the random file descriptor using the + new file's filename. (At this point, the sequential + file descriptor is closed.) */ + if (!wtap_fdreopen(cf->provider.wth, fname, &err)) { + cfile_open_failure_alert_box(fname, err, err_info); + cf_close(cf); + } else { + g_free(cf->filename); + cf->filename = g_strdup(fname); + cf->is_tempfile = FALSE; + } + cf_callback_invoke(cf_cb_file_fast_save_finished, cf); + break; + + case SAVE_WITH_WTAP: + /* Open and read the file we saved to. + + XXX - this is somewhat of a waste; we already have the + packets, all this gets us is updated file type information + (which we could just stuff into "cf"), and having the new + file be the one we have opened and from which we're reading + the data, and it means we have to spend time opening and + reading the file, which could be a significant amount of + time if the file is large. + + If the capture-file-writing code were to return the + seek offset of each packet it writes, we could save that + in the frame_data structure for the frame, and just open + the file without reading it again... + + ...as long as, for gzipped files, the process of writing + out the file *also* generates the information needed to + support fast random access to the compressed file. */ + /* rescan_file will cause us to try all open_routines, so + reset cfile's open_type */ + cf->open_type = WTAP_TYPE_AUTO; + /* There are cases when SAVE_WITH_WTAP can result in new packets + being written to the file, e.g ERF records + In that case, we need to reload the whole file */ + if(needs_reload) { + if (cf_open(cf, fname, WTAP_TYPE_AUTO, FALSE, &err) == CF_OK) { + if (cf_read(cf, /*reloading=*/TRUE) != CF_READ_OK) { + /* The rescan failed; just close the file. Either + a dialog was popped up for the failure, so the + user knows what happened, or they stopped the + rescan, in which case they know what happened. */ + /* XXX: This is inconsistent with normal open/reload behaviour. */ + cf_close(cf); + } + } + } + else { + if (rescan_file(cf, fname, FALSE) != CF_READ_OK) { + /* The rescan failed; just close the file. Either + a dialog was popped up for the failure, so the + user knows what happened, or they stopped the + rescan, in which case they know what happened. */ + cf_close(cf); + } + } + break; + } + + /* If we were told to discard the comments, do so. */ + if (discard_comments) { + /* Remove SHB comment, if any. */ + wtap_write_shb_comment(cf->provider.wth, NULL); + + /* remove all user comments */ + for (framenum = 1; framenum <= cf->count; framenum++) { + fdata = frame_data_sequence_find(cf->provider.frames, framenum); + + // XXX: This also ignores non-comment options like verdict + fdata->has_modified_block = FALSE; + } + + if (cf->provider.frames_modified_blocks) { + g_tree_destroy(cf->provider.frames_modified_blocks); + cf->provider.frames_modified_blocks = NULL; + } + + cf->packet_comment_count = 0; + } + } + return CF_WRITE_OK; + +fail: + if (fname_new != NULL) { + /* We were trying to write to a temporary file; get rid of it if it + exists. (We don't care whether this fails, as, if it fails, + there's not much we can do about it. I guess if it failed for + a reason other than "it doesn't exist", we could report an + error, so the user knows there's a junk file that they might + want to clean up.) */ + ws_unlink(fname_new); + g_free(fname_new); + } + cf_callback_invoke(cf_cb_file_save_failed, NULL); + return CF_WRITE_ERROR; +} + +cf_write_status_t +cf_export_specified_packets(capture_file *cf, const char *fname, + packet_range_t *range, guint save_format, + wtap_compression_type compression_type) +{ + gchar *fname_new = NULL; + int err; + gchar *err_info; + wtap_dumper *pdh; + save_callback_args_t callback_args; + wtap_dump_params params; + int encap; + + callback_args.export = TRUE; + packet_range_process_init(range); + + /* We're writing out specified packets from the specified capture + file to another file. Even if all captured packets are to be + written, don't special-case the operation - read each packet + and then write it out if it's one of the specified ones. */ + + wtap_dump_params_init(¶ms, cf->provider.wth); + + /* Determine what file encapsulation type we should use. */ + encap = wtap_dump_required_file_encap_type(cf->linktypes); + params.encap = encap; + + /* Use the snaplen from cf (XXX - does wtap_dump_params_init handle that?) */ + params.snaplen = cf->snap; + + if (file_exists(fname)) { + /* We're overwriting an existing file; write out to a new file, + and, if that succeeds, rename the new file on top of the + old file. That makes this a "safe save", so that we don't + lose the old file if we have a problem writing out the new + file. (If the existing file is the current capture file, + we *HAVE* to do that, otherwise we're overwriting the file + from which we're reading the packets that we're writing!) */ + fname_new = ws_strdup_printf("%s~", fname); + pdh = wtap_dump_open(fname_new, save_format, compression_type, ¶ms, + &err, &err_info); + } else { + pdh = wtap_dump_open(fname, save_format, compression_type, ¶ms, + &err, &err_info); + } + /* XXX idb_inf is documented to be used until wtap_dump_close. */ + g_free(params.idb_inf); + params.idb_inf = NULL; + + if (pdh == NULL) { + cfile_dump_open_failure_alert_box(fname, err, err_info, save_format); + goto fail; + } + + /* Add address resolution */ + wtap_dump_set_addrinfo_list(pdh, get_addrinfo_list()); + + /* Iterate through the list of packets, processing the packets we were + told to process. + + XXX - we've already called "packet_range_process_init(range)", but + "process_specified_records()" will do it again. Fortunately, + that's harmless in this case, as we haven't done anything to + "range" since we initialized it. */ + callback_args.pdh = pdh; + callback_args.fname = fname; + callback_args.file_type = save_format; + switch (process_specified_records(cf, range, "Writing", "specified records", + TRUE, save_record, &callback_args, TRUE)) { + + case PSP_FINISHED: + /* Completed successfully. */ + break; + + case PSP_STOPPED: + /* The user decided to abort the saving. + If we're writing to a temporary file, remove it. + XXX - should we do so even if we're not writing to a + temporary file? */ + wtap_dump_close(pdh, NULL, &err, &err_info); + if (fname_new != NULL) { + ws_unlink(fname_new); + g_free(fname_new); + } + wtap_dump_params_cleanup(¶ms); + + return CF_WRITE_ABORTED; + break; + + case PSP_FAILED: + /* Error while saving. */ + wtap_dump_close(pdh, NULL, &err, &err_info); + /* + * We don't report any error from closing; the error that caused + * process_specified_records() to fail has already been reported. + */ + goto fail; + } + + if (!wtap_dump_close(pdh, NULL, &err, &err_info)) { + cfile_close_failure_alert_box(fname, err, err_info); + goto fail; + } + + if (fname_new != NULL) { + /* We wrote out to fname_new, and should rename it on top of + fname; fname is now closed, so that should be possible even + on Windows. Do the rename. */ + if (ws_rename(fname_new, fname) == -1) { + /* Well, the rename failed. */ + cf_rename_failure_alert_box(fname, errno); + goto fail; + } + g_free(fname_new); + } + wtap_dump_params_cleanup(¶ms); + + return CF_WRITE_OK; + +fail: + if (fname_new != NULL) { + /* We were trying to write to a temporary file; get rid of it if it + exists. (We don't care whether this fails, as, if it fails, + there's not much we can do about it. I guess if it failed for + a reason other than "it doesn't exist", we could report an + error, so the user knows there's a junk file that they might + want to clean up.) */ + ws_unlink(fname_new); + g_free(fname_new); + } + wtap_dump_params_cleanup(¶ms); + + return CF_WRITE_ERROR; +} + +/* + * XXX - whether we mention the source pathname, the target pathname, + * or both depends on the error and on what we find if we look for + * one or both of them. + */ +static void +cf_rename_failure_alert_box(const char *filename, int err) +{ + gchar *display_basename; + + display_basename = g_filename_display_basename(filename); + switch (err) { + + case ENOENT: + /* XXX - should check whether the source exists and, if not, + report it as the problem and, if so, report the destination + as the problem. */ + simple_error_message_box("The path to the file \"%s\" doesn't exist.", + display_basename); + break; + + case EACCES: + /* XXX - if we're doing a rename after a safe save, we should + probably say something else. */ + simple_error_message_box("You don't have permission to move the capture file to \"%s\".", + display_basename); + break; + + default: + /* XXX - this should probably mention both the source and destination + pathnames. */ + simple_error_message_box("The file \"%s\" could not be moved: %s.", + display_basename, wtap_strerror(err)); + break; + } + g_free(display_basename); +} + +/* Reload the current capture file. */ +cf_status_t +cf_reload(capture_file *cf) +{ + gchar *filename; + gboolean is_tempfile; + cf_status_t cf_status = CF_OK; + int err; + + if (cf->read_lock) { + ws_warning("Failing cf_reload(\"%s\") since a read is in progress", cf->filename); + return CF_ERROR; + } + + /* If the file could be opened, "cf_open()" calls "cf_close()" + to get rid of state for the old capture file before filling in state + for the new capture file. "cf_close()" will remove the file if + it's a temporary file; we don't want that to happen (for one thing, + it'd prevent subsequent reopens from working). Remember whether it's + a temporary file, mark it as not being a temporary file, and then + reopen it as the type of file it was. + + Also, "cf_close()" will free "cf->filename", so we must make + a copy of it first. */ + filename = g_strdup(cf->filename); + is_tempfile = cf->is_tempfile; + cf->is_tempfile = FALSE; + if (cf_open(cf, filename, cf->open_type, is_tempfile, &err) == CF_OK) { + switch (cf_read(cf, /*reloading=*/TRUE)) { + + case CF_READ_OK: + case CF_READ_ERROR: + /* Just because we got an error, that doesn't mean we were unable + to read any of the file; we handle what we could get from the + file. */ + break; + + case CF_READ_ABORTED: + /* The user bailed out of re-reading the capture file; the + capture file has been closed. */ + break; + } + } else { + /* The open failed, so "cf->is_tempfile" wasn't set to "is_tempfile". + Instead, the file was left open, so we should restore "cf->is_tempfile" + ourselves. + + XXX - change the menu? Presumably "cf_open()" will do that; + make sure it does! */ + cf->is_tempfile = is_tempfile; + cf_status = CF_ERROR; + } + /* "cf_open()" made a copy of the file name we handed it, so + we should free up our copy. */ + g_free(filename); + return cf_status; +} -- cgit v1.2.3