This patch makes it possible to disable individual coloring rules
[obnox/wireshark/wip.git] / color_filters.c
1 /* color_filters.c
2  * Routines for color filters
3  *
4  * $Id$
5  *
6  * Wireshark - Network traffic analyzer
7  * By Gerald Combs <gerald@wireshark.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23  */
24 /*
25  * Updated 1 Dec 10 jjm
26  */
27  
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <glib.h>
33 #include <ctype.h>
34 #include <string.h>
35
36 #include <epan/filesystem.h>
37 #include "file_util.h"
38
39 #include <epan/packet.h>
40 #include "color.h"
41 #include "color_filters.h"
42 #include "file.h"
43 #include <epan/dfilter/dfilter.h>
44 #include "simple_dialog.h"
45 #include "ui_util.h"
46
47 static gboolean read_users_filters(GSList **cfl);
48
49 /* the currently active filters */
50 static GSList *color_filter_list = NULL;
51
52 /* keep "old" deleted filters in this list until 
53  * the dissection no longer needs them (e.g. file is closed) */
54 static GSList *color_filter_deleted_list = NULL;
55 static GSList *color_filter_valid_list = NULL;
56
57 /* Color Filters can en-/disabled. */
58 gboolean filters_enabled = TRUE;
59
60
61 /* Create a new filter */
62 color_filter_t *
63 color_filter_new(const gchar *name,    /* The name of the filter to create */
64                  const gchar *filter_string, /* The string representing the filter */
65                  color_t *bg_color,    /* The background color */
66                  color_t *fg_color,    /* The foreground color */
67                  gboolean disabled)     /* Is the filter disabled? */
68 {
69         color_filter_t *colorf;
70
71         colorf = g_malloc(sizeof (color_filter_t));
72         colorf->filter_name = g_strdup(name);
73         colorf->filter_text = g_strdup(filter_string);
74         colorf->bg_color = *bg_color;
75         colorf->fg_color = *fg_color;
76         colorf->disabled = disabled;
77         colorf->c_colorfilter = NULL;
78         colorf->edit_dialog = NULL;
79         colorf->selected = FALSE;
80         return colorf;
81 }
82
83 /* delete the specified filter */
84 void
85 color_filter_delete(color_filter_t *colorf)
86 {
87         if (colorf->filter_name != NULL)
88                 g_free(colorf->filter_name);
89         if (colorf->filter_text != NULL)
90                 g_free(colorf->filter_text);
91         if (colorf->c_colorfilter != NULL)
92                 dfilter_free(colorf->c_colorfilter);
93         g_free(colorf);
94 }
95
96 /* delete the specified filter (called from g_slist_foreach) */
97 static void
98 color_filter_delete_cb(gpointer filter_arg, gpointer unused _U_)
99 {
100         color_filter_t *colorf = filter_arg;
101
102         color_filter_delete(colorf);
103 }
104
105 /* delete the specified list */
106 void
107 color_filter_list_delete(GSList **cfl)
108 {
109         g_slist_foreach(*cfl, color_filter_delete_cb, NULL);
110         g_slist_free(*cfl);
111         *cfl = NULL;
112 }
113
114 /* clone a single list entries from normal to edit list */
115 static color_filter_t *
116 color_filter_clone(color_filter_t *colorf)
117 {
118         color_filter_t *new_colorf;
119
120         new_colorf = g_malloc(sizeof (color_filter_t));
121         new_colorf->filter_name = g_strdup(colorf->filter_name);
122         new_colorf->filter_text = g_strdup(colorf->filter_text);
123         new_colorf->bg_color = colorf->bg_color;
124         new_colorf->fg_color = colorf->fg_color;
125         new_colorf->disabled = colorf->disabled;
126         new_colorf->c_colorfilter = NULL;
127         new_colorf->edit_dialog = NULL;
128         new_colorf->selected = FALSE;
129
130         return new_colorf;
131 }
132
133 static void
134 color_filter_list_clone_cb(gpointer filter_arg, gpointer cfl_arg)
135 {
136     gpointer *cfl = cfl_arg;
137     color_filter_t *new_colorf;
138
139     new_colorf = color_filter_clone(filter_arg);
140     *cfl = g_slist_append(*cfl, new_colorf);
141 }
142
143 /* clone the specified list */
144 static GSList *
145 color_filter_list_clone(GSList *cfl)
146 {
147         GSList *new_list = NULL;
148
149         g_slist_foreach(cfl, color_filter_list_clone_cb, &new_list);
150
151         return new_list;
152 }
153
154 /* Initialize the filter structures (reading from file) for general running, including app startup */
155 void
156 color_filters_init(void)
157 {
158         /* delete all currently existing filters */
159         color_filter_list_delete(&color_filter_list);
160
161         /* try to read the users filters */
162         if (!read_users_filters(&color_filter_list))
163                 /* if that failed, try to read the global filters */
164                 color_filters_read_globals(&color_filter_list);
165 }
166
167 void
168 color_filters_cleanup(void)
169 {
170         /* delete the previously deleted filters */
171         color_filter_list_delete(&color_filter_deleted_list);
172 }
173
174 static void
175 color_filters_clone_cb(gpointer filter_arg, gpointer user_data)
176 {
177         color_filter_t * new_colorf = color_filter_clone(filter_arg);
178
179         color_filter_add_cb (new_colorf, user_data);
180 }
181
182 void
183 color_filters_clone(gpointer user_data)
184 {
185     g_slist_foreach(color_filter_list, color_filters_clone_cb, user_data);
186 }
187
188
189 static void
190 color_filter_compile_cb(gpointer filter_arg, gpointer unused _U_)
191 {
192         color_filter_t *colorf = filter_arg;
193
194         g_assert(colorf->c_colorfilter == NULL);
195         if (!dfilter_compile(colorf->filter_text, &colorf->c_colorfilter)) {
196                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
197                 "Could not compile color filter name: \"%s\" text: \"%s\".\n%s",
198                               colorf->filter_name, colorf->filter_text, dfilter_error_msg);
199                 /* this filter was compilable before, so this should never happen */
200                 /* except if the OK button of the parent window has been clicked */
201                 /* so don't use g_assert_not_reached() but check the filters again */
202         }
203 }
204
205 static void
206 color_filter_validate_cb(gpointer filter_arg, gpointer unused _U_)
207 {
208         color_filter_t *colorf = filter_arg;
209
210         g_assert(colorf->c_colorfilter == NULL);
211         if (!dfilter_compile(colorf->filter_text, &colorf->c_colorfilter)) {
212                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
213                 "Removing color filter name: \"%s\" text: \"%s\".\n%s",
214                               colorf->filter_name, colorf->filter_text, dfilter_error_msg);
215                 /* Delete the color filter from the list of color filters. */
216                 color_filter_valid_list = g_slist_remove(color_filter_valid_list, colorf);
217                 color_filter_delete(colorf);
218         }
219 }
220
221 /* apply changes from the edit list */
222 void
223 color_filters_apply(GSList *cfl)
224 {
225         /* "move" old entries to the deleted list
226          * we must keep them until the dissection no longer needs them */
227         color_filter_deleted_list = g_slist_concat(color_filter_deleted_list, color_filter_list);
228         color_filter_list = NULL;
229
230         /* clone all list entries from edit to normal list */
231         color_filter_valid_list = NULL;
232         color_filter_valid_list = color_filter_list_clone(cfl);
233
234         /* compile all filter */
235         g_slist_foreach(color_filter_valid_list, color_filter_validate_cb, NULL);
236
237         /* clone all list entries from edit to normal list */
238         color_filter_list = color_filter_list_clone(color_filter_valid_list);
239
240         /* compile all filter */
241         g_slist_foreach(color_filter_list, color_filter_compile_cb, NULL);
242 }
243
244 gboolean 
245 color_filters_used(void)
246 {
247         return color_filter_list != NULL && filters_enabled;
248 }
249
250 void
251 color_filters_enable(gboolean enable)
252 {
253         filters_enabled = enable;
254 }
255
256
257 /* prepare the epan_dissect_t for the filter */
258 static void
259 prime_edt(gpointer data, gpointer user_data)
260 {
261         color_filter_t  *colorf = data;
262         epan_dissect_t   *edt = user_data;
263
264         if (colorf->c_colorfilter != NULL)
265                 epan_dissect_prime_dfilter(edt, colorf->c_colorfilter);
266 }
267
268 /* Prime the epan_dissect_t with all the compiler
269  * color filters in 'color_filter_list'. */
270 void
271 color_filters_prime_edt(epan_dissect_t *edt)
272 {
273         g_slist_foreach(color_filter_list, prime_edt, edt);
274 }
275
276 /* Colorize a single packet of the packet list */
277 color_filter_t *
278 color_filters_colorize_packet(gint row, epan_dissect_t *edt)
279 {
280     GSList *curr;
281     color_filter_t *colorf;
282
283     /* If we have color filters, "search" for the matching one. */
284     if (color_filters_used()) {
285         curr = color_filter_list;
286
287         while(curr != NULL) {
288             colorf = curr->data;
289             if ((colorf->c_colorfilter != NULL) &&
290                 (!colorf->disabled) &&
291                  dfilter_apply_edt(colorf->c_colorfilter, edt)) {
292                     /* this is the filter to use, apply it to the packet list */
293                     packet_list_set_colors(row, &(colorf->fg_color), &(colorf->bg_color));
294                     return colorf;
295             }
296             curr = g_slist_next(curr);
297         }
298     }
299
300     return NULL;
301 }
302
303 /* read filters from the given file */
304 /* XXX - Would it make more sense to use GStrings here instead of reallocing
305    our buffers? */
306 static gboolean
307 read_filters_file(FILE *f, gpointer user_data)
308 {
309 #define INIT_BUF_SIZE 128
310         gchar  *name = NULL;
311         gchar  *filter_exp = NULL;
312         guint32 name_len = INIT_BUF_SIZE;
313         guint32 filter_exp_len = INIT_BUF_SIZE;
314         guint32 i = 0;
315         gint32  c;
316         guint16 fg_r, fg_g, fg_b, bg_r, bg_g, bg_b;
317         gboolean disabled = FALSE;
318         gboolean skip_end_of_line = FALSE;
319
320         name = g_malloc(name_len + 1);
321         filter_exp = g_malloc(filter_exp_len + 1);
322
323         while (1) {
324
325                 if (skip_end_of_line) {
326                         do {
327                                 c = getc(f);
328                         } while (c != EOF && c != '\n');
329                         if (c == EOF)
330                                 break;
331                         disabled = FALSE;
332                         skip_end_of_line = FALSE;
333                 }
334
335                 while ((c = getc(f)) != EOF && isspace(c)) {
336                         if (c == '\n') {
337                                 continue;
338                         }
339                 }
340
341                 if (c == EOF)
342                         break;
343
344                 if (c == '!') {
345                         disabled = TRUE;
346                         continue;
347                 }
348
349                 /* skip # comments and invalid lines */
350                 if (c != '@') { 
351                         skip_end_of_line = TRUE;
352                         continue;
353                 }
354
355                 /* we get the @ delimiter.
356                  * Format is:
357                  * @name@filter expression@[background r,g,b][foreground r,g,b]
358                  */
359
360                 /* retrieve name */
361                 i = 0;
362                 while (1) {
363                         c = getc(f);
364                         if (c == EOF || c == '@')
365                                 break;
366                         if (i >= name_len) {
367                                 /* buffer isn't long enough; double its length.*/
368                                 name_len *= 2;
369                                 name = g_realloc(name, name_len + 1);
370                         }
371                         name[i++] = c;            
372                 }
373                 name[i] = '\0';
374
375                 if (c == EOF) {
376                         break;
377                 } else if (i == 0) {
378                         skip_end_of_line = TRUE;
379                         continue;
380                 }
381
382                 /* retrieve filter expression */
383                 i = 0;
384                 while (1) {
385                         c = getc(f);
386                         if (c == EOF || c == '@')
387                                 break;
388                         if (i >= filter_exp_len) {
389                                 /* buffer isn't long enough; double its length.*/
390                                 filter_exp_len *= 2;
391                                 filter_exp = g_realloc(filter_exp, filter_exp_len + 1);
392                         }
393                         filter_exp[i++] = c;
394                 }
395                 filter_exp[i] = '\0';
396
397                 if (c == EOF) {
398                         break;
399                 } else if (i == 0) {
400                         skip_end_of_line = TRUE;
401                         continue;
402                 }
403
404                 /* retrieve background and foreground colors */
405                 if (fscanf(f,"[%hu,%hu,%hu][%hu,%hu,%hu]",
406                         &bg_r, &bg_g, &bg_b, &fg_r, &fg_g, &fg_b) == 6) {
407
408                         /* we got a complete color filter */
409
410                         color_t bg_color, fg_color;
411                         color_filter_t *colorf;
412                         dfilter_t *temp_dfilter;
413
414                         if (!dfilter_compile(filter_exp, &temp_dfilter)) {
415                                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
416                                 "Could not compile color filter %s from saved filters.\n%s",
417                                               name, dfilter_error_msg);
418                                 skip_end_of_line = TRUE;
419                                 continue;
420                         }
421
422                         if (!initialize_color(&fg_color, fg_r, fg_g, fg_b)) {
423                                 /* oops */
424                                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
425                                     "Could not allocate foreground color "
426                                     "specified in input file for %s.", name);
427                                 dfilter_free(temp_dfilter);
428                                 skip_end_of_line = TRUE;
429                                 continue;
430                         }
431                         if (!initialize_color(&bg_color, bg_r, bg_g, bg_b)) {
432                                 /* oops */
433                                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
434                                     "Could not allocate background color "
435                                     "specified in input file for %s.", name);
436                                 dfilter_free(temp_dfilter);
437                                 skip_end_of_line = TRUE;
438                                 continue;
439                         }
440
441                         colorf = color_filter_new(name, filter_exp, &bg_color,
442                             &fg_color, disabled);
443                         if(user_data == &color_filter_list) {
444                                 GSList **cfl = (GSList **)user_data;
445
446                                 /* internal call */
447                                 colorf->c_colorfilter = temp_dfilter;
448                                 *cfl = g_slist_append(*cfl, colorf);
449                         } else {
450                                 /* external call */
451                                 /* just editing, don't need the compiled filter */
452                                 dfilter_free(temp_dfilter);
453                                 color_filter_add_cb (colorf, user_data);
454                         }
455                 }    /* if sscanf */
456
457                 skip_end_of_line = TRUE;
458         }
459
460         g_free(name);
461         g_free(filter_exp);
462         return TRUE;
463 }
464
465 /* read filters from the user's filter file */
466 static gboolean
467 read_users_filters(GSList **cfl)
468 {
469         gchar *path;
470         FILE *f;
471         gboolean ret;
472
473         /* decide what file to open (from dfilter code) */
474         path = get_persconffile_path("colorfilters", FALSE);
475         if ((f = eth_fopen(path, "r")) == NULL) {
476                 if (errno != ENOENT) {
477                         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
478                             "Could not open filter file\n\"%s\": %s.", path,
479                             strerror(errno));
480                 }
481                 g_free(path);
482                 return FALSE;
483         }
484         g_free(path);
485         path = NULL;
486
487         ret = read_filters_file(f, cfl);
488         fclose(f);
489         return ret;
490 }
491
492 /* read filters from the filter file */
493 gboolean
494 color_filters_read_globals(gpointer user_data)
495 {
496         gchar *path;
497         FILE *f;
498         gboolean ret;
499
500         /* decide what file to open (from dfilter code) */
501         path = get_datafile_path("colorfilters");
502         if ((f = eth_fopen(path, "r")) == NULL) {
503                 if (errno != ENOENT) {
504                         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
505                             "Could not open global filter file\n\"%s\": %s.", path,
506                             strerror(errno));
507                 }
508                 g_free(path);
509                 return FALSE;
510         }
511         g_free(path);
512         path = NULL;
513
514         ret = read_filters_file(f, user_data);
515         fclose(f);
516         return ret;
517 }
518
519 /* read filters from some other filter file (import) */
520 gboolean
521 color_filters_import(gchar *path, gpointer user_data)
522 {
523         FILE *f;
524         gboolean ret;
525
526         if ((f = eth_fopen(path, "r")) == NULL) {
527                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
528                     "Could not open\n%s\nfor reading: %s.",
529                     path, strerror(errno));
530                 return FALSE;
531         }
532
533         ret = read_filters_file(f, user_data);
534         fclose(f);
535         return ret;
536 }
537
538 struct write_filter_data
539 {
540   FILE * f;
541   gboolean only_selected;
542 };
543
544 /* save a single filter */
545 static void
546 write_filter(gpointer filter_arg, gpointer data_arg)
547 {
548         struct write_filter_data *data = data_arg;
549         color_filter_t *colorf = filter_arg;
550         FILE *f = data->f;
551
552         if (colorf->selected || !data->only_selected) {
553                 fprintf(f,"%s@%s@%s@[%d,%d,%d][%d,%d,%d]\n",
554                     colorf->disabled ? "!" : "",
555                     colorf->filter_name,
556                     colorf->filter_text,
557                     colorf->bg_color.red,
558                     colorf->bg_color.green,
559                     colorf->bg_color.blue,
560                     colorf->fg_color.red,
561                     colorf->fg_color.green,
562                     colorf->fg_color.blue);
563         }
564 }
565
566 /* save filters in a filter file */
567 static gboolean
568 write_filters_file(GSList *cfl, FILE *f, gboolean only_selected)
569 {
570         struct write_filter_data data;
571
572         data.f = f;
573         data.only_selected = only_selected;
574   
575         fprintf(f,"# DO NOT EDIT THIS FILE!  It was created by Wireshark\n");
576         g_slist_foreach(cfl, write_filter, &data);
577         return TRUE;
578 }
579
580 /* save filters in users filter file */
581 gboolean
582 color_filters_write(GSList *cfl)
583 {
584         gchar *pf_dir_path;
585         const gchar *path;
586         FILE *f;
587
588         /* Create the directory that holds personal configuration files,
589            if necessary.  */
590         if (create_persconffile_dir(&pf_dir_path) == -1) {
591                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
592                     "Can't create directory\n\"%s\"\nfor color files: %s.",
593                     pf_dir_path, strerror(errno));
594                 g_free(pf_dir_path);
595                 return FALSE;
596         }
597
598         path = get_persconffile_path("colorfilters", TRUE);
599         if ((f = eth_fopen(path, "w+")) == NULL) {
600                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
601                     "Could not open\n%s\nfor writing: %s.",
602                     path, strerror(errno));
603                 return FALSE;
604         }
605         write_filters_file(cfl, f, FALSE);
606         fclose(f);
607         return TRUE;
608 }
609
610 /* save filters in some other filter file (export) */
611 gboolean
612 color_filters_export(gchar *path, GSList *cfl, gboolean only_marked)
613 {
614         FILE *f;
615
616         if ((f = eth_fopen(path, "w+")) == NULL) {
617                 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
618                     "Could not open\n%s\nfor writing: %s.",
619                     path, strerror(errno));
620                 return FALSE;
621         }
622         write_filters_file(cfl, f, only_marked);
623         fclose(f);
624         return TRUE;
625 }
626