20800366dd
Change all wireshark.org URLs to use https. Fix some broken links while we're at it. Change-Id: I161bf8eeca43b8027605acea666032da86f5ea1c Reviewed-on: https://code.wireshark.org/review/34089 Reviewed-by: Guy Harris <guy@alum.mit.edu>
645 lines
17 KiB
C
645 lines
17 KiB
C
/* filter_files.c
|
|
* Code for reading and writing the filters file.
|
|
*
|
|
* Wireshark - Network traffic analyzer
|
|
* By Gerald Combs <gerald@wireshark.org>
|
|
* Copyright 1998 Gerald Combs
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include <wsutil/file_util.h>
|
|
#include <wsutil/filesystem.h>
|
|
#include <wsutil/report_message.h>
|
|
|
|
#include "ui/filter_files.h"
|
|
|
|
/*
|
|
* List of capture filters - saved.
|
|
*/
|
|
static GList *capture_filters = NULL;
|
|
|
|
/*
|
|
* List of display filters - saved.
|
|
*/
|
|
static GList *display_filters = NULL;
|
|
|
|
/*
|
|
* List of capture filters - currently edited.
|
|
*/
|
|
static GList *capture_edited_filters = NULL;
|
|
|
|
/*
|
|
* List of display filters - currently edited.
|
|
*/
|
|
static GList *display_edited_filters = NULL;
|
|
|
|
/*
|
|
* Read in a list of filters.
|
|
*
|
|
* On error, report the error via the UI.
|
|
*/
|
|
|
|
#define INIT_BUF_SIZE 128
|
|
|
|
static GList *
|
|
add_filter_entry(GList *fl, const char *filt_name, const char *filt_expr)
|
|
{
|
|
filter_def *filt;
|
|
|
|
filt = (filter_def *) g_malloc(sizeof(filter_def));
|
|
filt->name = g_strdup(filt_name);
|
|
filt->strval = g_strdup(filt_expr);
|
|
return g_list_prepend(fl, filt);
|
|
}
|
|
|
|
static void
|
|
free_filter_entry(gpointer data)
|
|
{
|
|
filter_def *filt = (filter_def*)data;
|
|
g_free(filt->name);
|
|
g_free(filt->strval);
|
|
g_free(filt);
|
|
}
|
|
|
|
void free_filter_lists(void)
|
|
{
|
|
if (capture_filters) {
|
|
g_list_free_full(capture_filters, free_filter_entry);
|
|
capture_filters = NULL;
|
|
}
|
|
if (display_filters) {
|
|
g_list_free_full(display_filters, free_filter_entry);
|
|
display_filters = NULL;
|
|
}
|
|
if (capture_edited_filters) {
|
|
g_list_free_full(capture_edited_filters, free_filter_entry);
|
|
capture_edited_filters = NULL;
|
|
}
|
|
if (display_edited_filters) {
|
|
g_list_free_full(display_edited_filters, free_filter_entry);
|
|
display_edited_filters = NULL;
|
|
}
|
|
}
|
|
|
|
static GList *
|
|
remove_filter_entry(GList *fl, GList *fl_entry)
|
|
{
|
|
filter_def *filt;
|
|
|
|
filt = (filter_def *) fl_entry->data;
|
|
g_free(filt->name);
|
|
g_free(filt->strval);
|
|
g_free(filt);
|
|
return g_list_remove_link(fl, fl_entry);
|
|
}
|
|
|
|
static int
|
|
skip_whitespace(FILE *ff)
|
|
{
|
|
int c;
|
|
|
|
while ((c = getc(ff)) != EOF && c != '\n' && g_ascii_isspace(c))
|
|
;
|
|
return c;
|
|
}
|
|
|
|
static int
|
|
getc_crlf(FILE *ff)
|
|
{
|
|
int c;
|
|
|
|
c = getc(ff);
|
|
if (c == '\r') {
|
|
/* Treat CR-LF at the end of a line like LF, so that if we're reading
|
|
* a Windows-format file on UN*X, we handle it the same way we'd handle
|
|
* a UN*X-format file. */
|
|
c = getc(ff);
|
|
if (c != EOF && c != '\n') {
|
|
/* Put back the character after the CR, and process the CR normally. */
|
|
ungetc(c, ff);
|
|
c = '\r';
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
|
|
void
|
|
read_filter_list(filter_list_type_t list_type)
|
|
{
|
|
const char *ff_name, *ff_description;
|
|
char *ff_path;
|
|
FILE *ff;
|
|
GList **flpp;
|
|
int c;
|
|
char *filt_name, *filt_expr;
|
|
int filt_name_len, filt_expr_len;
|
|
int filt_name_index, filt_expr_index;
|
|
int line = 1;
|
|
|
|
switch (list_type) {
|
|
|
|
case CFILTER_LIST:
|
|
ff_name = CFILTER_FILE_NAME;
|
|
ff_description = "capture";
|
|
flpp = &capture_filters;
|
|
break;
|
|
|
|
case DFILTER_LIST:
|
|
ff_name = DFILTER_FILE_NAME;
|
|
ff_description = "display";
|
|
flpp = &display_filters;
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached();
|
|
return;
|
|
}
|
|
|
|
/* try to open personal "cfilters"/"dfilters" file */
|
|
ff_path = get_persconffile_path(ff_name, TRUE);
|
|
if ((ff = ws_fopen(ff_path, "r")) == NULL) {
|
|
/*
|
|
* Did that fail because the file didn't exist?
|
|
*/
|
|
if (errno != ENOENT) {
|
|
/*
|
|
* No. Just give up.
|
|
*/
|
|
report_warning("Could not open your %s filter file\n\"%s\": %s.",
|
|
ff_description, ff_path, g_strerror(errno));
|
|
g_free(ff_path);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Yes. See if there's an "old style" personal "filters" file; if so, read it.
|
|
* This means that a user will start out with their capture and
|
|
* display filter lists being identical; each list may contain
|
|
* filters that don't belong in that list. The user can edit
|
|
* the filter lists, and delete the ones that don't belong in
|
|
* a particular list.
|
|
*/
|
|
g_free(ff_path);
|
|
ff_path = get_persconffile_path(FILTER_FILE_NAME, FALSE);
|
|
if ((ff = ws_fopen(ff_path, "r")) == NULL) {
|
|
/*
|
|
* Did that fail because the file didn't exist?
|
|
*/
|
|
if (errno != ENOENT) {
|
|
/*
|
|
* No. Just give up.
|
|
*/
|
|
report_warning("Could not open your %s filter file\n\"%s\": %s.",
|
|
ff_description, ff_path, g_strerror(errno));
|
|
g_free(ff_path);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Try to open the global "cfilters/dfilters" file.
|
|
*/
|
|
g_free(ff_path);
|
|
ff_path = get_datafile_path(ff_name);
|
|
if ((ff = ws_fopen(ff_path, "r")) == NULL) {
|
|
/*
|
|
* Well, that didn't work, either. Just give up.
|
|
* Report an error if the file existed but we couldn't open it.
|
|
*/
|
|
if (errno != ENOENT) {
|
|
report_warning("Could not open your %s filter file\n\"%s\": %s.",
|
|
ff_description, ff_path, g_strerror(errno));
|
|
}
|
|
g_free(ff_path);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If we already have a list of filters, discard it. */
|
|
/* this should never happen - this function is called only once for each list! */
|
|
while(*flpp) {
|
|
*flpp = remove_filter_entry(*flpp, g_list_first(*flpp));
|
|
}
|
|
|
|
/* Allocate the filter name buffer. */
|
|
filt_name_len = INIT_BUF_SIZE;
|
|
filt_name = (char *)g_malloc(filt_name_len + 1);
|
|
filt_expr_len = INIT_BUF_SIZE;
|
|
filt_expr = (char *)g_malloc(filt_expr_len + 1);
|
|
|
|
for (line = 1; ; line++) {
|
|
/* Lines in a filter file are of the form
|
|
|
|
"name" expression
|
|
|
|
where "name" is a name, in quotes - backslashes in the name
|
|
escape the next character, so quotes and backslashes can appear
|
|
in the name - and "expression" is a filter expression, not in
|
|
quotes, running to the end of the line. */
|
|
|
|
/* Skip over leading white space, if any. */
|
|
c = skip_whitespace(ff);
|
|
|
|
if (c == EOF)
|
|
break; /* Nothing more to read */
|
|
if (c == '\n')
|
|
continue; /* Blank line. */
|
|
|
|
/* "c" is the first non-white-space character.
|
|
If it's not a quote, it's an error. */
|
|
if (c != '"') {
|
|
g_warning("'%s' line %d doesn't have a quoted filter name.", ff_path,
|
|
line);
|
|
while (c != '\n')
|
|
c = getc(ff); /* skip to the end of the line */
|
|
continue;
|
|
}
|
|
|
|
/* Get the name of the filter. */
|
|
filt_name_index = 0;
|
|
for (;;) {
|
|
c = getc_crlf(ff);
|
|
if (c == EOF || c == '\n')
|
|
break; /* End of line - or end of file */
|
|
if (c == '"') {
|
|
/* Closing quote. */
|
|
if (filt_name_index >= filt_name_len) {
|
|
/* Filter name buffer isn't long enough; double its length. */
|
|
filt_name_len *= 2;
|
|
filt_name = (char *)g_realloc(filt_name, filt_name_len + 1);
|
|
}
|
|
filt_name[filt_name_index] = '\0';
|
|
break;
|
|
}
|
|
if (c == '\\') {
|
|
/* Next character is escaped */
|
|
c = getc_crlf(ff);
|
|
if (c == EOF || c == '\n')
|
|
break; /* End of line - or end of file */
|
|
}
|
|
/* Add this character to the filter name string. */
|
|
if (filt_name_index >= filt_name_len) {
|
|
/* Filter name buffer isn't long enough; double its length. */
|
|
filt_name_len *= 2;
|
|
filt_name = (char *)g_realloc(filt_name, filt_name_len + 1);
|
|
}
|
|
filt_name[filt_name_index] = c;
|
|
filt_name_index++;
|
|
}
|
|
|
|
if (c == EOF) {
|
|
if (!ferror(ff)) {
|
|
/* EOF, not error; no newline seen before EOF */
|
|
g_warning("'%s' line %d doesn't have a newline.", ff_path,
|
|
line);
|
|
}
|
|
break; /* nothing more to read */
|
|
}
|
|
|
|
if (c != '"') {
|
|
/* No newline seen before end-of-line */
|
|
g_warning("'%s' line %d doesn't have a closing quote.", ff_path,
|
|
line);
|
|
continue;
|
|
}
|
|
|
|
/* Skip over separating white space, if any. */
|
|
c = skip_whitespace(ff);
|
|
|
|
if (c == EOF) {
|
|
if (!ferror(ff)) {
|
|
/* EOF, not error; no newline seen before EOF */
|
|
g_warning("'%s' line %d doesn't have a newline.", ff_path,
|
|
line);
|
|
}
|
|
break; /* nothing more to read */
|
|
}
|
|
|
|
if (c == '\n') {
|
|
/* No filter expression */
|
|
g_warning("'%s' line %d doesn't have a filter expression.", ff_path,
|
|
line);
|
|
continue;
|
|
}
|
|
|
|
/* "c" is the first non-white-space character; it's the first
|
|
character of the filter expression. */
|
|
filt_expr_index = 0;
|
|
for (;;) {
|
|
/* Add this character to the filter expression string. */
|
|
if (filt_expr_index >= filt_expr_len) {
|
|
/* Filter expressioin buffer isn't long enough; double its length. */
|
|
filt_expr_len *= 2;
|
|
filt_expr = (char *)g_realloc(filt_expr, filt_expr_len + 1);
|
|
}
|
|
filt_expr[filt_expr_index] = c;
|
|
filt_expr_index++;
|
|
|
|
/* Get the next character. */
|
|
c = getc_crlf(ff);
|
|
if (c == EOF || c == '\n')
|
|
break;
|
|
}
|
|
|
|
if (c == EOF) {
|
|
if (!ferror(ff)) {
|
|
/* EOF, not error; no newline seen before EOF */
|
|
g_warning("'%s' line %d doesn't have a newline.", ff_path,
|
|
line);
|
|
}
|
|
break; /* nothing more to read */
|
|
}
|
|
|
|
/* We saw the ending newline; terminate the filter expression string */
|
|
if (filt_expr_index >= filt_expr_len) {
|
|
/* Filter expressioin buffer isn't long enough; double its length. */
|
|
filt_expr_len *= 2;
|
|
filt_expr = (char *)g_realloc(filt_expr, filt_expr_len + 1);
|
|
}
|
|
filt_expr[filt_expr_index] = '\0';
|
|
|
|
/* Add the new filter to the list of filters */
|
|
*flpp = add_filter_entry(*flpp, filt_name, filt_expr);
|
|
}
|
|
if (ferror(ff)) {
|
|
report_warning("Error reading your %s filter file\n\"%s\": %s.",
|
|
ff_description, ff_path, g_strerror(errno));
|
|
}
|
|
g_free(ff_path);
|
|
fclose(ff);
|
|
g_free(filt_name);
|
|
g_free(filt_expr);
|
|
|
|
/* init the corresponding edited list */
|
|
switch (list_type) {
|
|
case CFILTER_LIST:
|
|
copy_filter_list(CFILTER_EDITED_LIST, CFILTER_LIST);
|
|
break;
|
|
case DFILTER_LIST:
|
|
copy_filter_list(DFILTER_EDITED_LIST, DFILTER_LIST);
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get a pointer to a list of filters.
|
|
*/
|
|
static GList **
|
|
get_filter_list(filter_list_type_t list_type)
|
|
{
|
|
GList **flpp;
|
|
|
|
switch (list_type) {
|
|
|
|
case CFILTER_LIST:
|
|
flpp = &capture_filters;
|
|
break;
|
|
|
|
case DFILTER_LIST:
|
|
flpp = &display_filters;
|
|
break;
|
|
|
|
case CFILTER_EDITED_LIST:
|
|
flpp = &capture_edited_filters;
|
|
break;
|
|
|
|
case DFILTER_EDITED_LIST:
|
|
flpp = &display_edited_filters;
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached();
|
|
flpp = NULL;
|
|
}
|
|
return flpp;
|
|
}
|
|
|
|
/*
|
|
* Get a pointer to the first entry in a filter list.
|
|
*/
|
|
GList *
|
|
get_filter_list_first(filter_list_type_t list_type)
|
|
{
|
|
GList **flpp;
|
|
|
|
flpp = get_filter_list(list_type);
|
|
return g_list_first(*flpp);
|
|
}
|
|
|
|
/*
|
|
* Add a new filter to the end of a list.
|
|
* Returns a pointer to the newly-added entry.
|
|
*/
|
|
GList *
|
|
add_to_filter_list(filter_list_type_t list_type, const char *name,
|
|
const char *expression)
|
|
{
|
|
GList **flpp;
|
|
|
|
flpp = get_filter_list(list_type);
|
|
*flpp = add_filter_entry(*flpp, name, expression);
|
|
|
|
return g_list_last(*flpp);
|
|
}
|
|
|
|
/*
|
|
* Remove a filter from a list.
|
|
*/
|
|
void
|
|
remove_from_filter_list(filter_list_type_t list_type, GList *fl_entry)
|
|
{
|
|
GList **flpp;
|
|
|
|
flpp = get_filter_list(list_type);
|
|
*flpp = remove_filter_entry(*flpp, fl_entry);
|
|
}
|
|
|
|
/*
|
|
* Write out a list of filters.
|
|
*
|
|
* On error, report the error via the UI.
|
|
*/
|
|
void
|
|
save_filter_list(filter_list_type_t list_type)
|
|
{
|
|
char *pf_dir_path;
|
|
const gchar *ff_name, *ff_description;
|
|
gchar *ff_path, *ff_path_new;
|
|
GList *fl;
|
|
GList *flpp;
|
|
filter_def *filt;
|
|
FILE *ff;
|
|
guchar *p, c;
|
|
|
|
switch (list_type) {
|
|
|
|
case CFILTER_LIST:
|
|
ff_name = CFILTER_FILE_NAME;
|
|
ff_description = "capture";
|
|
fl = capture_filters;
|
|
break;
|
|
|
|
case DFILTER_LIST:
|
|
ff_name = DFILTER_FILE_NAME;
|
|
ff_description = "display";
|
|
fl = display_filters;
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached();
|
|
return;
|
|
}
|
|
|
|
/* Create the directory that holds personal configuration files,
|
|
if necessary. */
|
|
if (create_persconffile_dir(&pf_dir_path) == -1) {
|
|
report_failure("Can't create directory\n\"%s\"\nfor filter files: %s.",
|
|
pf_dir_path, g_strerror(errno));
|
|
g_free(pf_dir_path);
|
|
return;
|
|
}
|
|
|
|
ff_path = get_persconffile_path(ff_name, TRUE);
|
|
|
|
/* Write to "XXX.new", and rename if that succeeds.
|
|
That means we don't trash the file if we fail to write it out
|
|
completely. */
|
|
ff_path_new = g_strdup_printf("%s.new", ff_path);
|
|
|
|
if ((ff = ws_fopen(ff_path_new, "w")) == NULL) {
|
|
/* We had an error saving the filter. */
|
|
report_failure("Error saving your %s filter file\nCouldn't open \"%s\": %s.",
|
|
ff_description, ff_path_new, g_strerror(errno));
|
|
g_free(ff_path_new);
|
|
g_free(ff_path);
|
|
return;
|
|
}
|
|
flpp = g_list_first(fl);
|
|
while (flpp) {
|
|
filt = (filter_def *) flpp->data;
|
|
|
|
/* Write out the filter name as a quoted string; escape any quotes
|
|
or backslashes. */
|
|
putc('"', ff);
|
|
for (p = (guchar *)filt->name; (c = *p) != '\0'; p++) {
|
|
if (c == '"' || c == '\\')
|
|
putc('\\', ff);
|
|
putc(c, ff);
|
|
}
|
|
putc('"', ff);
|
|
|
|
/* Separate the filter name and value with a space. */
|
|
putc(' ', ff);
|
|
|
|
/* Write out the filter expression and a newline. */
|
|
fprintf(ff, "%s\n", filt->strval);
|
|
if (ferror(ff)) {
|
|
report_failure("Error saving your %s filter file\nWrite to \"%s\" failed: %s.",
|
|
ff_description, ff_path_new, g_strerror(errno));
|
|
fclose(ff);
|
|
ws_unlink(ff_path_new);
|
|
g_free(ff_path_new);
|
|
g_free(ff_path);
|
|
return;
|
|
}
|
|
flpp = flpp->next;
|
|
}
|
|
if (fclose(ff) == EOF) {
|
|
report_failure("Error saving your %s filter file\nWrite to \"%s\" failed: %s.",
|
|
ff_description, ff_path_new, g_strerror(errno));
|
|
ws_unlink(ff_path_new);
|
|
g_free(ff_path_new);
|
|
g_free(ff_path);
|
|
return;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
/* ANSI C doesn't say whether "rename()" removes the target if it
|
|
exists; the Win32 call to rename files doesn't do so, which I
|
|
infer is the reason why the MSVC++ "rename()" doesn't do so.
|
|
We must therefore remove the target file first, on Windows.
|
|
|
|
XXX - ws_rename() should be ws_stdio_rename() on Windows,
|
|
and ws_stdio_rename() uses MoveFileEx() with MOVEFILE_REPLACE_EXISTING,
|
|
so it should remove the target if it exists, so this stuff
|
|
shouldn't be necessary. Perhaps it dates back to when we were
|
|
calling rename(), with that being a wrapper around Microsoft's
|
|
_rename(), which didn't remove the target. */
|
|
if (ws_remove(ff_path) < 0 && errno != ENOENT) {
|
|
/* It failed for some reason other than "it's not there"; if
|
|
it's not there, we don't need to remove it, so we just
|
|
drive on. */
|
|
report_failure("Error saving your %s filter file\nCouldn't remove \"%s\": %s.",
|
|
ff_description, ff_path, g_strerror(errno));
|
|
ws_unlink(ff_path_new);
|
|
g_free(ff_path_new);
|
|
g_free(ff_path);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (ws_rename(ff_path_new, ff_path) < 0) {
|
|
report_failure("Error saving your %s filter file\nCouldn't rename \"%s\" to \"%s\": %s.",
|
|
ff_description, ff_path_new, ff_path, g_strerror(errno));
|
|
ws_unlink(ff_path_new);
|
|
g_free(ff_path_new);
|
|
g_free(ff_path);
|
|
return;
|
|
}
|
|
g_free(ff_path_new);
|
|
g_free(ff_path);
|
|
}
|
|
|
|
/*
|
|
* Copy a filter list into another.
|
|
*/
|
|
void copy_filter_list(filter_list_type_t dest_type, filter_list_type_t src_type)
|
|
{
|
|
GList **flpp_dest;
|
|
GList **flpp_src;
|
|
GList *flp_src;
|
|
filter_def *filt;
|
|
|
|
g_assert(dest_type != src_type);
|
|
|
|
flpp_dest = get_filter_list(dest_type);
|
|
flpp_src = get_filter_list(src_type);
|
|
/* throw away the "old" destination list - a NULL list is ok here */
|
|
while(*flpp_dest) {
|
|
*flpp_dest = remove_filter_entry(*flpp_dest, g_list_first(*flpp_dest));
|
|
}
|
|
g_assert(g_list_length(*flpp_dest) == 0);
|
|
|
|
/* copy the list entries */
|
|
for(flp_src = g_list_first(*flpp_src); flp_src; flp_src = g_list_next(flp_src)) {
|
|
filt = (filter_def *)(flp_src->data);
|
|
|
|
*flpp_dest = add_filter_entry(*flpp_dest, filt->name, filt->strval);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Editor modelines - https://www.wireshark.org/tools/modelines.html
|
|
*
|
|
* Local Variables:
|
|
* c-basic-offset: 2
|
|
* tab-width: 8
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*
|
|
* ex: set shiftwidth=2 tabstop=8 expandtab:
|
|
* :indentSize=2:tabSize=8:noTabs=true:
|
|
*/
|