Introduce tags as alternative way to specify which
[privoxy.git] / actions.c
1 const char actions_rcs[] = "$Id: actions.c,v 1.37 2007/03/11 15:56:12 fabiankeil Exp $";
2 /*********************************************************************
3  *
4  * File        :  $Source: /cvsroot/ijbswa/current/actions.c,v $
5  *
6  * Purpose     :  Declares functions to work with actions files
7  *                Functions declared include: FIXME
8  *
9  * Copyright   :  Written by and Copyright (C) 2001-2007 the SourceForge
10  *                Privoxy team. http://www.privoxy.org/
11  *
12  *                Based on the Internet Junkbuster originally written
13  *                by and Copyright (C) 1997 Anonymous Coders and
14  *                Junkbusters Corporation.  http://www.junkbusters.com
15  *
16  *                This program is free software; you can redistribute it
17  *                and/or modify it under the terms of the GNU General
18  *                Public License as published by the Free Software
19  *                Foundation; either version 2 of the License, or (at
20  *                your option) any later version.
21  *
22  *                This program is distributed in the hope that it will
23  *                be useful, but WITHOUT ANY WARRANTY; without even the
24  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
25  *                PARTICULAR PURPOSE.  See the GNU General Public
26  *                License for more details.
27  *
28  *                The GNU General Public License should be included with
29  *                this file.  If not, you can view it at
30  *                http://www.gnu.org/copyleft/gpl.html
31  *                or write to the Free Software Foundation, Inc., 59
32  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
33  *
34  * Revisions   :
35  *    $Log: actions.c,v $
36  *    Revision 1.37  2007/03/11 15:56:12  fabiankeil
37  *    Add kludge to log unknown aliases and actions before exiting.
38  *
39  *    Revision 1.36  2006/12/28 17:15:42  fabiankeil
40  *    Fix gcc43 conversion warning.
41  *
42  *    Revision 1.35  2006/07/18 14:48:45  david__schmidt
43  *    Reorganizing the repository: swapping out what was HEAD (the old 3.1 branch)
44  *    with what was really the latest development (the v_3_0_branch branch)
45  *
46  *    Revision 1.32.2.6  2006/01/29 23:10:56  david__schmidt
47  *    Multiple filter file support
48  *
49  *    Revision 1.32.2.5  2005/06/09 01:18:41  david__schmidt
50  *    Tweaks to conditionally include pthread.h if FEATURE_PTHREAD is enabled -
51  *    this becomes important when jcc.h gets included later down the line.
52  *
53  *    Revision 1.32.2.4  2003/12/03 10:33:11  oes
54  *    - Implemented Privoxy version requirement through
55  *      for-privoxy-version= statement in {{settings}}
56  *      block
57  *    - Fix for unchecked out-of-memory condition
58  *
59  *    Revision 1.32.2.3  2003/02/28 12:52:10  oes
60  *    Fixed memory leak reported by Dan Price in Bug #694713
61  *
62  *    Revision 1.32.2.2  2002/11/20 14:36:55  oes
63  *    Extended unload_current_actions_file() to multiple AFs.
64  *    Thanks to Oliver Stoeneberg for the hint.
65  *
66  *    Revision 1.32.2.1  2002/05/26 12:13:16  roro
67  *    Change unsigned to unsigned long in actions_name struct.  This closes
68  *    SourceForge Bug #539284.
69  *
70  *    Revision 1.32  2002/05/12 21:36:29  jongfoster
71  *    Correcting function comments
72  *
73  *    Revision 1.31  2002/05/06 07:56:50  oes
74  *    Made actions_to_html independent of FEATURE_CGI_EDIT_ACTIONS
75  *
76  *    Revision 1.30  2002/04/30 11:14:52  oes
77  *    Made csp the first parameter in *action_to_html
78  *
79  *    Revision 1.29  2002/04/26 19:30:54  jongfoster
80  *    - current_action_to_html(): Adding help link for the "-" form of
81  *      one-string actions.
82  *    - Some actions had "<br>-", some "<br> -" (note the space).
83  *      Standardizing on no space.
84  *    - Greatly simplifying some of the code by using string_join()
85  *      where appropriate.
86  *
87  *    Revision 1.28  2002/04/26 12:53:15  oes
88  *     - CGI AF editor now writes action lines split into
89  *       single lines with line continuation
90  *     - actions_to_html now embeds each action name in
91  *       link to chapter
92  *     - current_action_to_text is now called current_action_to_html
93  *       and acts like actions_to_html
94  *
95  *    Revision 1.27  2002/04/24 02:10:31  oes
96  *     - Jon's patch for multiple AFs:
97  *       - split load_actions_file, add load_one_actions_file
98  *       - make csp->actions_list files an array
99  *       - remember file id with each action
100  *     - Copy_action now frees dest action before copying
101  *
102  *    Revision 1.26  2002/03/26 22:29:54  swa
103  *    we have a new homepage!
104  *
105  *    Revision 1.25  2002/03/24 13:25:43  swa
106  *    name change related issues
107  *
108  *    Revision 1.24  2002/03/16 23:54:06  jongfoster
109  *    Adding graceful termination feature, to help look for memory leaks.
110  *    If you enable this (which, by design, has to be done by hand
111  *    editing config.h) and then go to http://i.j.b/die, then the program
112  *    will exit cleanly after the *next* request.  It should free all the
113  *    memory that was used.
114  *
115  *    Revision 1.23  2002/03/07 03:46:16  oes
116  *    Fixed compiler warnings
117  *
118  *    Revision 1.22  2002/01/21 00:27:02  jongfoster
119  *    Allowing free_action(NULL).
120  *    Moving the functions that #include actionlist.h to the end of the file,
121  *    because the Visual C++ 97 debugger gets extremely confused if you try
122  *    to debug any code that comes after them in the file.
123  *
124  *    Revision 1.21  2002/01/17 20:54:44  jongfoster
125  *    Renaming free_url to free_url_spec, since it frees a struct url_spec.
126  *
127  *    Revision 1.20  2001/11/22 21:56:49  jongfoster
128  *    Making action_spec->flags into an unsigned long rather than just an
129  *    unsigned int.
130  *    Fixing a bug in the display of -add-header and -wafer
131  *
132  *    Revision 1.19  2001/11/13 00:14:07  jongfoster
133  *    Fixing stupid bug now I've figured out what || means.
134  *    (It always returns 0 or 1, not one of it's paramaters.)
135  *
136  *    Revision 1.18  2001/11/07 00:06:06  steudten
137  *    Add line number in error output for lineparsing for
138  *    actionsfile.
139  *
140  *    Revision 1.17  2001/10/25 03:40:47  david__schmidt
141  *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
142  *    threads to call select() simultaneously.  So, it's time to do a real, live,
143  *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
144  *    (native). Both versions will work, but using __OS2__ offers multi-threading.
145  *
146  *    Revision 1.16  2001/10/23 21:30:30  jongfoster
147  *    Adding error-checking to selected functions.
148  *
149  *    Revision 1.15  2001/10/14 21:58:22  jongfoster
150  *    Adding support for the CGI-based editor:
151  *    - Exported get_actions()
152  *    - Added new function free_alias_list()
153  *    - Added support for {{settings}} and {{description}} blocks
154  *      in the actions file.  They are currently ignored.
155  *    - Added restriction to only one {{alias}} block which must appear
156  *      first in the file, to simplify the editor's rewriting rules.
157  *    - Note that load_actions_file() is no longer used by the CGI-based
158  *      editor, but some of the other routines in this file are.
159  *
160  *    Revision 1.14  2001/09/22 16:36:59  jongfoster
161  *    Removing unused parameter fs from read_config_line()
162  *
163  *    Revision 1.13  2001/09/16 15:47:37  jongfoster
164  *    First version of CGI-based edit interface.  This is very much a
165  *    work-in-progress, and you can't actually use it to edit anything
166  *    yet.  You must #define FEATURE_CGI_EDIT_ACTIONS for these changes
167  *    to have any effect.
168  *
169  *    Revision 1.12  2001/09/16 13:21:27  jongfoster
170  *    Changes to use new list functions.
171  *
172  *    Revision 1.11  2001/09/14 00:17:32  jongfoster
173  *    Tidying up memory allocation. New function init_action().
174  *
175  *    Revision 1.10  2001/09/10 10:14:34  oes
176  *    Removing unused variable
177  *
178  *    Revision 1.9  2001/07/30 22:08:36  jongfoster
179  *    Tidying up #defines:
180  *    - All feature #defines are now of the form FEATURE_xxx
181  *    - Permanently turned off WIN_GUI_EDIT
182  *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
183  *
184  *    Revision 1.8  2001/06/29 13:19:52  oes
185  *    Removed logentry from cancelled commit
186  *
187  *    Revision 1.7  2001/06/09 10:55:28  jongfoster
188  *    Changing BUFSIZ ==> BUFFER_SIZE
189  *
190  *    Revision 1.6  2001/06/07 23:04:34  jongfoster
191  *    Made get_actions() static.
192  *
193  *    Revision 1.5  2001/06/03 19:11:48  oes
194  *    adapted to new enlist_unique arg format
195  *
196  *    Revision 1.4  2001/06/01 20:03:42  jongfoster
197  *    Better memory management - current_action->strings[] now
198  *    contains copies of the strings, not the original.
199  *
200  *    Revision 1.3  2001/06/01 18:49:17  jongfoster
201  *    Replaced "list_share" with "list" - the tiny memory gain was not
202  *    worth the extra complexity.
203  *
204  *    Revision 1.2  2001/05/31 21:40:00  jongfoster
205  *    Removing some commented out, obsolete blocks of code.
206  *
207  *    Revision 1.1  2001/05/31 21:16:46  jongfoster
208  *    Moved functions to process the action list into this new file.
209  *
210  *
211  *********************************************************************/
212 \f
213
214 #include "config.h"
215
216 #include <stdio.h>
217 #include <string.h>
218 #include <assert.h>
219 #include <stdlib.h>
220
221 #ifdef FEATURE_PTHREAD
222 #include <pthread.h>
223 #endif
224
225 #include "project.h"
226 #include "jcc.h"
227 #include "list.h"
228 #include "actions.h"
229 #include "miscutil.h"
230 #include "errlog.h"
231 #include "loaders.h"
232 #include "encode.h"
233 #include "urlmatch.h"
234 #include "cgi.h"
235 #include "ssplit.h"
236
237 const char actions_h_rcs[] = ACTIONS_H_VERSION;
238
239
240 /*
241  * We need the main list of options.
242  *
243  * First, we need a way to tell between boolean, string, and multi-string
244  * options.  For string and multistring options, we also need to be
245  * able to tell the difference between a "+" and a "-".  (For bools,
246  * the "+"/"-" information is encoded in "add" and "mask").  So we use
247  * an enumerated type (well, the preprocessor equivalent).  Here are
248  * the values:
249  */
250 #define AV_NONE       0 /* +opt -opt */
251 #define AV_ADD_STRING 1 /* +stropt{string} */
252 #define AV_REM_STRING 2 /* -stropt */
253 #define AV_ADD_MULTI  3 /* +multiopt{string} +multiopt{string2} */
254 #define AV_REM_MULTI  4 /* -multiopt{string} -multiopt          */
255
256 /*
257  * We need a structure to hold the name, flag changes,
258  * type, and string index.
259  */
260 struct action_name
261 {
262    const char * name;
263    unsigned long mask;   /* a bit set to "0" = remove action */
264    unsigned long add;    /* a bit set to "1" = add action */
265    int takes_value;      /* an AV_... constant */
266    int index;            /* index into strings[] or multi[] */
267 };
268
269 /*
270  * And with those building blocks in place, here's the array.
271  */
272 static const struct action_name action_names[] =
273 {
274    /*
275     * Well actually there's no data here - it's in actionlist.h
276     * This keeps it together to make it easy to change.
277     *
278     * Here's the macros used to format it:
279     */
280 #define DEFINE_ACTION_MULTI(name,index)                   \
281    { "+" name, ACTION_MASK_ALL, 0, AV_ADD_MULTI, index }, \
282    { "-" name, ACTION_MASK_ALL, 0, AV_REM_MULTI, index },
283 #define DEFINE_ACTION_STRING(name,flag,index)                 \
284    { "+" name, ACTION_MASK_ALL, flag, AV_ADD_STRING, index }, \
285    { "-" name, ~flag, 0, AV_REM_STRING, index },
286 #define DEFINE_ACTION_BOOL(name,flag)   \
287    { "+" name, ACTION_MASK_ALL, flag }, \
288    { "-" name, ~flag, 0 },
289 #define DEFINE_ACTION_ALIAS 1 /* Want aliases please */
290
291 #include "actionlist.h"
292
293 #undef DEFINE_ACTION_MULTI
294 #undef DEFINE_ACTION_STRING
295 #undef DEFINE_ACTION_BOOL
296 #undef DEFINE_ACTION_ALIAS
297
298    { NULL, 0, 0 } /* End marker */
299 };
300
301
302 static int load_one_actions_file(struct client_state *csp, int fileid);
303
304
305 /*********************************************************************
306  *
307  * Function    :  merge_actions
308  *
309  * Description :  Merge two actions together.
310  *                Similar to "dest += src".
311  *
312  * Parameters  :
313  *          1  :  dest = Actions to modify.
314  *          2  :  src = Action to add.
315  *
316  * Returns     :  JB_ERR_OK or JB_ERR_MEMORY
317  *
318  *********************************************************************/
319 jb_err merge_actions (struct action_spec *dest,
320                       const struct action_spec *src)
321 {
322    int i;
323    jb_err err;
324
325    dest->mask &= src->mask;
326    dest->add  &= src->mask;
327    dest->add  |= src->add;
328
329    for (i = 0; i < ACTION_STRING_COUNT; i++)
330    {
331       char * str = src->string[i];
332       if (str)
333       {
334          freez(dest->string[i]);
335          dest->string[i] = strdup(str);
336          if (NULL == dest->string[i])
337          {
338             return JB_ERR_MEMORY;
339          }
340       }
341    }
342
343    for (i = 0; i < ACTION_MULTI_COUNT; i++)
344    {
345       if (src->multi_remove_all[i])
346       {
347          /* Remove everything from dest */
348          list_remove_all(dest->multi_remove[i]);
349          dest->multi_remove_all[i] = 1;
350
351          err = list_duplicate(dest->multi_add[i], src->multi_add[i]);
352       }
353       else if (dest->multi_remove_all[i])
354       {
355          /*
356           * dest already removes everything, so we only need to worry
357           * about what we add.
358           */
359          list_remove_list(dest->multi_add[i], src->multi_remove[i]);
360          err = list_append_list_unique(dest->multi_add[i], src->multi_add[i]);
361       }
362       else
363       {
364          /* No "remove all"s to worry about. */
365          list_remove_list(dest->multi_add[i], src->multi_remove[i]);
366          err = list_append_list_unique(dest->multi_remove[i], src->multi_remove[i]);
367          if (!err) err = list_append_list_unique(dest->multi_add[i], src->multi_add[i]);
368       }
369
370       if (err)
371       {
372          return err;
373       }
374    }
375
376    return JB_ERR_OK;
377 }
378
379
380 /*********************************************************************
381  *
382  * Function    :  copy_action
383  *
384  * Description :  Copy an action_specs.
385  *                Similar to "dest = src".
386  *
387  * Parameters  :
388  *          1  :  dest = Destination of copy.
389  *          2  :  src = Source for copy.
390  *
391  * Returns     :  N/A
392  *
393  *********************************************************************/
394 jb_err copy_action (struct action_spec *dest,
395                     const struct action_spec *src)
396 {
397    int i;
398    jb_err err = JB_ERR_OK;
399
400    free_action(dest);
401    memset(dest, '\0', sizeof(*dest));
402
403    dest->mask = src->mask;
404    dest->add  = src->add;
405
406    for (i = 0; i < ACTION_STRING_COUNT; i++)
407    {
408       char * str = src->string[i];
409       if (str)
410       {
411          str = strdup(str);
412          if (!str)
413          {
414             return JB_ERR_MEMORY;
415          }
416          dest->string[i] = str;
417       }
418    }
419
420    for (i = 0; i < ACTION_MULTI_COUNT; i++)
421    {
422       dest->multi_remove_all[i] = src->multi_remove_all[i];
423       err = list_duplicate(dest->multi_remove[i], src->multi_remove[i]);
424       if (err)
425       {
426          return err;
427       }
428       err = list_duplicate(dest->multi_add[i],    src->multi_add[i]);
429       if (err)
430       {
431          return err;
432       }
433    }
434    return err;
435 }
436
437
438 /*********************************************************************
439  *
440  * Function    :  free_action
441  *
442  * Description :  Destroy an action_spec.  Frees memory used by it,
443  *                except for the memory used by the struct action_spec
444  *                itself.
445  *
446  * Parameters  :
447  *          1  :  src = Source to free.
448  *
449  * Returns     :  N/A
450  *
451  *********************************************************************/
452 void free_action (struct action_spec *src)
453 {
454    int i;
455
456    if (src == NULL)
457    {
458       return;
459    }
460
461    for (i = 0; i < ACTION_STRING_COUNT; i++)
462    {
463       freez(src->string[i]);
464    }
465
466    for (i = 0; i < ACTION_MULTI_COUNT; i++)
467    {
468       destroy_list(src->multi_remove[i]);
469       destroy_list(src->multi_add[i]);
470    }
471
472    memset(src, '\0', sizeof(*src));
473 }
474
475
476 /*********************************************************************
477  *
478  * Function    :  get_action_token
479  *
480  * Description :  Parses a line for the first action.
481  *                Modifies it's input array, doesn't allocate memory.
482  *                e.g. given:
483  *                *line="  +abc{def}  -ghi "
484  *                Returns:
485  *                *line="  -ghi "
486  *                *name="+abc"
487  *                *value="def"
488  *
489  * Parameters  :
490  *          1  :  line = [in] The line containing the action.
491  *                       [out] Start of next action on line, or
492  *                       NULL if we reached the end of line before
493  *                       we found an action.
494  *          2  :  name = [out] Start of action name, null
495  *                       terminated.  NULL on EOL
496  *          3  :  value = [out] Start of action value, null
497  *                        terminated.  NULL if none or EOL.
498  *
499  * Returns     :  JB_ERR_OK => Ok
500  *                JB_ERR_PARSE => Mismatched {} (line was trashed anyway)
501  *
502  *********************************************************************/
503 jb_err get_action_token(char **line, char **name, char **value)
504 {
505    char * str = *line;
506    char ch;
507
508    /* set default returns */
509    *line = NULL;
510    *name = NULL;
511    *value = NULL;
512
513    /* Eat any leading whitespace */
514    while ((*str == ' ') || (*str == '\t'))
515    {
516       str++;
517    }
518
519    if (*str == '\0')
520    {
521       return 0;
522    }
523
524    if (*str == '{')
525    {
526       /* null name, just value is prohibited */
527       return JB_ERR_PARSE;
528    }
529
530    *name = str;
531
532    /* parse option */
533    while (((ch = *str) != '\0') &&
534           (ch != ' ') && (ch != '\t') && (ch != '{'))
535    {
536       if (ch == '}')
537       {
538          /* error, '}' without '{' */
539          return JB_ERR_PARSE;
540       }
541       str++;
542    }
543    *str = '\0';
544
545    if (ch != '{')
546    {
547       /* no value */
548       if (ch == '\0')
549       {
550          /* EOL - be careful not to run off buffer */
551          *line = str;
552       }
553       else
554       {
555          /* More to parse next time. */
556          *line = str + 1;
557       }
558       return JB_ERR_OK;
559    }
560
561    str++;
562    *value = str;
563
564    str = strchr(str, '}');
565    if (str == NULL)
566    {
567       /* error */
568       *value = NULL;
569       return JB_ERR_PARSE;
570    }
571
572    /* got value */
573    *str = '\0';
574    *line = str + 1;
575
576    chomp(*value);
577
578    return JB_ERR_OK;
579 }
580
581
582 /*********************************************************************
583  *
584  * Function    :  get_actions
585  *
586  * Description :  Parses a list of actions.
587  *
588  * Parameters  :
589  *          1  :  line = The string containing the actions.
590  *                       Will be written to by this function.
591  *          2  :  alias_list = Custom alias list, or NULL for none.
592  *          3  :  cur_action = Where to store the action.  Caller
593  *                             allocates memory.
594  *
595  * Returns     :  JB_ERR_OK => Ok
596  *                JB_ERR_PARSE => Parse error (line was trashed anyway)
597  *                nonzero => Out of memory (line was trashed anyway)
598  *
599  *********************************************************************/
600 jb_err get_actions(char *line,
601                    struct action_alias * alias_list,
602                    struct action_spec *cur_action)
603 {
604    jb_err err;
605    init_action(cur_action);
606    cur_action->mask = ACTION_MASK_ALL;
607
608    while (line)
609    {
610       char * option = NULL;
611       char * value = NULL;
612
613       err = get_action_token(&line, &option, &value);
614       if (err)
615       {
616          return err;
617       }
618
619       if (option)
620       {
621          /* handle option in 'option' */
622
623          /* Check for standard action name */
624          const struct action_name * action = action_names;
625
626          while ( (action->name != NULL) && (0 != strcmpic(action->name, option)) )
627          {
628             action++;
629          }
630          if (action->name != NULL)
631          {
632             /* Found it */
633             cur_action->mask &= action->mask;
634             cur_action->add  &= action->mask;
635             cur_action->add  |= action->add;
636
637             switch (action->takes_value)
638             {
639             case AV_NONE:
640                /* ignore any option. */
641                break;
642             case AV_ADD_STRING:
643                {
644                   /* add single string. */
645
646                   if ((value == NULL) || (*value == '\0'))
647                   {
648                      return JB_ERR_PARSE;
649                   }
650                   /* FIXME: should validate option string here */
651                   freez (cur_action->string[action->index]);
652                   cur_action->string[action->index] = strdup(value);
653                   if (NULL == cur_action->string[action->index])
654                   {
655                      return JB_ERR_MEMORY;
656                   }
657                   break;
658                }
659             case AV_REM_STRING:
660                {
661                   /* remove single string. */
662
663                   freez (cur_action->string[action->index]);
664                   break;
665                }
666             case AV_ADD_MULTI:
667                {
668                   /* append multi string. */
669
670                   struct list * remove_p = cur_action->multi_remove[action->index];
671                   struct list * add_p    = cur_action->multi_add[action->index];
672
673                   if ((value == NULL) || (*value == '\0'))
674                   {
675                      return JB_ERR_PARSE;
676                   }
677
678                   list_remove_item(remove_p, value);
679                   err = enlist_unique(add_p, value, 0);
680                   if (err)
681                   {
682                      return err;
683                   }
684                   break;
685                }
686             case AV_REM_MULTI:
687                {
688                   /* remove multi string. */
689
690                   struct list * remove_p = cur_action->multi_remove[action->index];
691                   struct list * add_p    = cur_action->multi_add[action->index];
692
693                   if ( (value == NULL) || (*value == '\0')
694                      || ((*value == '*') && (value[1] == '\0')) )
695                   {
696                      /*
697                       * no option, or option == "*".
698                       *
699                       * Remove *ALL*.
700                       */
701                      list_remove_all(remove_p);
702                      list_remove_all(add_p);
703                      cur_action->multi_remove_all[action->index] = 1;
704                   }
705                   else
706                   {
707                      /* Valid option - remove only 1 option */
708
709                      if ( !cur_action->multi_remove_all[action->index] )
710                      {
711                         /* there isn't a catch-all in the remove list already */
712                         err = enlist_unique(remove_p, value, 0);
713                         if (err)
714                         {
715                            return err;
716                         }
717                      }
718                      list_remove_item(add_p, value);
719                   }
720                   break;
721                }
722             default:
723                /* Shouldn't get here unless there's memory corruption. */
724                assert(0);
725                return JB_ERR_PARSE;
726             }
727          }
728          else
729          {
730             /* try user aliases. */
731             const struct action_alias * alias = alias_list;
732
733             while ( (alias != NULL) && (0 != strcmpic(alias->name, option)) )
734             {
735                alias = alias->next;
736             }
737             if (alias != NULL)
738             {
739                /* Found it */
740                merge_actions(cur_action, alias->action);
741             }
742             else
743             {
744                /* Bad action name */
745                /*
746                 * XXX: This is a fatal error and Privoxy will later on exit
747                 * in load_one_actions_file() because of an "invalid line".
748                 *
749                 * It would be preferable to name the offending option in that
750                 * error message, but currently there is no way to do that and
751                 * we have to live with two error messages for basically the
752                 * same reason.
753                 */
754                log_error(LOG_LEVEL_ERROR, "Unknown action or alias: %s", option);
755                return JB_ERR_PARSE;
756             }
757          }
758       }
759    }
760
761    return JB_ERR_OK;
762 }
763
764
765 /*********************************************************************
766  *
767  * Function    :  init_current_action
768  *
769  * Description :  Zero out an action.
770  *
771  * Parameters  :
772  *          1  :  dest = An uninitialized current_action_spec.
773  *
774  * Returns     :  N/A
775  *
776  *********************************************************************/
777 void init_current_action (struct current_action_spec *dest)
778 {
779    memset(dest, '\0', sizeof(*dest));
780
781    dest->flags = ACTION_MOST_COMPATIBLE;
782 }
783
784
785 /*********************************************************************
786  *
787  * Function    :  init_action
788  *
789  * Description :  Zero out an action.
790  *
791  * Parameters  :
792  *          1  :  dest = An uninitialized action_spec.
793  *
794  * Returns     :  N/A
795  *
796  *********************************************************************/
797 void init_action (struct action_spec *dest)
798 {
799    memset(dest, '\0', sizeof(*dest));
800 }
801
802
803 /*********************************************************************
804  *
805  * Function    :  merge_current_action
806  *
807  * Description :  Merge two actions together.
808  *                Similar to "dest += src".
809  *                Differences between this and merge_actions()
810  *                is that this one doesn't allocate memory for
811  *                strings (so "src" better be in memory for at least
812  *                as long as "dest" is, and you'd better free
813  *                "dest" using "free_current_action").
814  *                Also, there is no  mask or remove lists in dest.
815  *                (If we're applying it to a URL, we don't need them)
816  *
817  * Parameters  :
818  *          1  :  dest = Current actions, to modify.
819  *          2  :  src = Action to add.
820  *
821  * Returns  0  :  no error
822  *        !=0  :  error, probably JB_ERR_MEMORY.
823  *
824  *********************************************************************/
825 jb_err merge_current_action (struct current_action_spec *dest,
826                              const struct action_spec *src)
827 {
828    int i;
829    jb_err err = JB_ERR_OK;
830
831    dest->flags  &= src->mask;
832    dest->flags  |= src->add;
833
834    for (i = 0; i < ACTION_STRING_COUNT; i++)
835    {
836       char * str = src->string[i];
837       if (str)
838       {
839          str = strdup(str);
840          if (!str)
841          {
842             return JB_ERR_MEMORY;
843          }
844          freez(dest->string[i]);
845          dest->string[i] = str;
846       }
847    }
848
849    for (i = 0; i < ACTION_MULTI_COUNT; i++)
850    {
851       if (src->multi_remove_all[i])
852       {
853          /* Remove everything from dest, then add src->multi_add */
854          err = list_duplicate(dest->multi[i], src->multi_add[i]);
855          if (err)
856          {
857             return err;
858          }
859       }
860       else
861       {
862          list_remove_list(dest->multi[i], src->multi_remove[i]);
863          err = list_append_list_unique(dest->multi[i], src->multi_add[i]);
864          if (err)
865          {
866             return err;
867          }
868       }
869    }
870    return err;
871 }
872
873
874 /*********************************************************************
875  *
876  * Function    :  update_action_bits
877  *
878  * Description :  Updates the action bits based on matching tags.
879  *
880  * Parameters  :
881  *          1  :  csp = Current client state (buffers, headers, etc...)
882  *
883  * Returns     :  0 if no tag matched, or
884  *                1 otherwise
885  *
886  *********************************************************************/
887 int update_action_bits(struct client_state *csp)
888 {
889    struct file_list *fl;
890    struct url_actions *b;
891    struct list_entry *tag;
892    int updated = 0;
893    int i;
894
895    /* Take each tag, */
896    for (tag = csp->tags->first; tag != NULL; tag = tag->next)
897    {
898       /* run through all action files, */
899       for (i = 0; i < MAX_AF_FILES; i++)
900       {
901          if (((fl = csp->actions_list[i]) == NULL) || ((b = fl->f) == NULL))
902          {
903             /* Skip empty files */
904             continue;
905          }
906          /* and through all the action patterns, */
907          for (b = b->next; NULL != b; b = b->next)
908          {
909             /* skip the URL patterns, */
910             if (NULL == b->url->tag_regex)
911             {
912                continue;
913             }
914
915             /* and check if one of the tag patterns matches this tag, */
916             if (0 == regexec(b->url->tag_regex, tag->str, 0, NULL, 0))
917             {
918                /* if it does, update the action bit map, */
919                if (merge_current_action(csp->action, b->action))
920                {
921                   log_error(LOG_LEVEL_ERROR,
922                      "Out of memorey while changing action bits");
923                }
924                /* and signal the change. */
925                updated = 1;
926             }
927          }
928       }
929    }
930
931    return updated;
932 }
933
934
935 /*********************************************************************
936  *
937  * Function    :  free_current_action
938  *
939  * Description :  Free memory used by a current_action_spec.
940  *                Does not free the current_action_spec itself.
941  *
942  * Parameters  :
943  *          1  :  src = Source to free.
944  *
945  * Returns     :  N/A
946  *
947  *********************************************************************/
948 void free_current_action (struct current_action_spec *src)
949 {
950    int i;
951
952    for (i = 0; i < ACTION_STRING_COUNT; i++)
953    {
954       freez(src->string[i]);
955    }
956
957    for (i = 0; i < ACTION_MULTI_COUNT; i++)
958    {
959       destroy_list(src->multi[i]);
960    }
961
962    memset(src, '\0', sizeof(*src));
963 }
964
965
966 static struct file_list *current_actions_file[MAX_AF_FILES]  = {
967    NULL, NULL, NULL, NULL, NULL,
968    NULL, NULL, NULL, NULL, NULL
969 };
970
971
972 #ifdef FEATURE_GRACEFUL_TERMINATION
973 /*********************************************************************
974  *
975  * Function    :  unload_current_actions_file
976  *
977  * Description :  Unloads current actions file - reset to state at
978  *                beginning of program.
979  *
980  * Parameters  :  None
981  *
982  * Returns     :  N/A
983  *
984  *********************************************************************/
985 void unload_current_actions_file(void)
986 {
987    int i;
988
989    for (i = 0; i < MAX_AF_FILES; i++)
990    {
991       if (current_actions_file[i])
992       {
993          current_actions_file[i]->unloader = unload_actions_file;
994          current_actions_file[i] = NULL;
995       }
996    }
997 }
998 #endif /* FEATURE_GRACEFUL_TERMINATION */
999
1000
1001 /*********************************************************************
1002  *
1003  * Function    :  unload_actions_file
1004  *
1005  * Description :  Unloads an actions module.
1006  *
1007  * Parameters  :
1008  *          1  :  file_data = the data structure associated with the
1009  *                            actions file.
1010  *
1011  * Returns     :  N/A
1012  *
1013  *********************************************************************/
1014 void unload_actions_file(void *file_data)
1015 {
1016    struct url_actions * next;
1017    struct url_actions * cur = (struct url_actions *)file_data;
1018    while (cur != NULL)
1019    {
1020       next = cur->next;
1021       free_url_spec(cur->url);
1022       free_action(cur->action);
1023       freez(cur);
1024       cur = next;
1025    }
1026
1027 }
1028
1029
1030 /*********************************************************************
1031  *
1032  * Function    :  free_alias_list
1033  *
1034  * Description :  Free memory used by a list of aliases.
1035  *
1036  * Parameters  :
1037  *          1  :  alias_list = Linked list to free.
1038  *
1039  * Returns     :  N/A
1040  *
1041  *********************************************************************/
1042 void free_alias_list(struct action_alias *alias_list)
1043 {
1044    while (alias_list != NULL)
1045    {
1046       struct action_alias * next = alias_list->next;
1047       alias_list->next = NULL;
1048       freez(alias_list->name);
1049       free_action(alias_list->action);
1050       free(alias_list);
1051       alias_list = next;
1052    }
1053 }
1054
1055
1056 /*********************************************************************
1057  *
1058  * Function    :  load_actions_file
1059  *
1060  * Description :  Read and parse all the action files and add to files
1061  *                list.
1062  *
1063  * Parameters  :
1064  *          1  :  csp = Current client state (buffers, headers, etc...)
1065  *
1066  * Returns     :  0 => Ok, everything else is an error.
1067  *
1068  *********************************************************************/
1069 int load_actions_file(struct client_state *csp)
1070 {
1071    int i;
1072    int result;
1073
1074    for (i = 0; i < MAX_AF_FILES; i++)
1075    {
1076       if (csp->config->actions_file[i])
1077       {
1078          result = load_one_actions_file(csp, i);
1079          if (result)
1080          {
1081             return result;
1082          }
1083       }
1084       else if (current_actions_file[i])
1085       {
1086          current_actions_file[i]->unloader = unload_actions_file;
1087          current_actions_file[i] = NULL;
1088       }
1089    }
1090
1091    return 0;
1092 }
1093
1094 /*********************************************************************
1095  *
1096  * Function    :  load_one_actions_file
1097  *
1098  * Description :  Read and parse a action file and add to files
1099  *                list.
1100  *
1101  * Parameters  :
1102  *          1  :  csp = Current client state (buffers, headers, etc...)
1103  *          2  :  fileid = File index to load.
1104  *
1105  * Returns     :  0 => Ok, everything else is an error.
1106  *
1107  *********************************************************************/
1108 static int load_one_actions_file(struct client_state *csp, int fileid)
1109 {
1110
1111    /*
1112     * Parser mode.
1113     * Note: Keep these in the order they occur in the file, they are
1114     * sometimes tested with <=
1115     */
1116 #define MODE_START_OF_FILE 1
1117 #define MODE_SETTINGS      2
1118 #define MODE_DESCRIPTION   3
1119 #define MODE_ALIAS         4
1120 #define MODE_ACTIONS       5
1121
1122    int mode = MODE_START_OF_FILE;
1123
1124    FILE *fp;
1125    struct url_actions *last_perm;
1126    struct url_actions *perm;
1127    char  buf[BUFFER_SIZE];
1128    struct file_list *fs;
1129    struct action_spec * cur_action = NULL;
1130    int cur_action_used = 0;
1131    struct action_alias * alias_list = NULL;
1132    unsigned long linenum = 0;
1133
1134    if (!check_file_changed(current_actions_file[fileid], csp->config->actions_file[fileid], &fs))
1135    {
1136       /* No need to load */
1137       csp->actions_list[fileid] = current_actions_file[fileid];
1138       return 0;
1139    }
1140    if (!fs)
1141    {
1142       log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': error finding file: %E",
1143                 csp->config->actions_file[fileid]);
1144       return 1; /* never get here */
1145    }
1146
1147    fs->f = last_perm = (struct url_actions *)zalloc(sizeof(*last_perm));
1148    if (last_perm == NULL)
1149    {
1150       log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': out of memory!",
1151                 csp->config->actions_file[fileid]);
1152       return 1; /* never get here */
1153    }
1154
1155    if ((fp = fopen(csp->config->actions_file[fileid], "r")) == NULL)
1156    {
1157       log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': error opening file: %E",
1158                 csp->config->actions_file[fileid]);
1159       return 1; /* never get here */
1160    }
1161
1162    while (read_config_line(buf, sizeof(buf), fp, &linenum) != NULL)
1163    {
1164       if (*buf == '{')
1165       {
1166          /* It's a header block */
1167          if (buf[1] == '{')
1168          {
1169             /* It's {{settings}} or {{alias}} */
1170             size_t len = strlen(buf);
1171             char * start = buf + 2;
1172             char * end = buf + len - 1;
1173             if ((len < 5) || (*end-- != '}') || (*end-- != '}'))
1174             {
1175                /* too short */
1176                fclose(fp);
1177                log_error(LOG_LEVEL_FATAL,
1178                   "can't load actions file '%s': invalid line (%lu): %s", 
1179                   csp->config->actions_file[fileid], linenum, buf);
1180                return 1; /* never get here */
1181             }
1182
1183             /* Trim leading and trailing whitespace. */
1184             end[1] = '\0';
1185             chomp(start);
1186
1187             if (*start == '\0')
1188             {
1189                /* too short */
1190                fclose(fp);
1191                log_error(LOG_LEVEL_FATAL,
1192                   "can't load actions file '%s': invalid line (%lu): {{ }}",
1193                   csp->config->actions_file[fileid], linenum);
1194                return 1; /* never get here */
1195             }
1196
1197             /*
1198              * An actionsfile can optionally contain the following blocks.
1199              * They *MUST* be in this order, to simplify processing:
1200              *
1201              * {{settings}}
1202              * name=value...
1203              *
1204              * {{description}}
1205              * ...free text, format TBD, but no line may start with a '{'...
1206              *
1207              * {{alias}}
1208              * name=actions...
1209              *
1210              * The actual actions must be *after* these special blocks.
1211              * None of these special blocks may be repeated.
1212              *
1213              */
1214             if (0 == strcmpic(start, "settings"))
1215             {
1216                /* it's a {{settings}} block */
1217                if (mode >= MODE_SETTINGS)
1218                {
1219                   /* {{settings}} must be first thing in file and must only
1220                    * appear once.
1221                    */
1222                   fclose(fp);
1223                   log_error(LOG_LEVEL_FATAL,
1224                      "can't load actions file '%s': line %lu: {{settings}} must only appear once, and it must be before anything else.",
1225                      csp->config->actions_file[fileid], linenum);
1226                }
1227                mode = MODE_SETTINGS;
1228             }
1229             else if (0 == strcmpic(start, "description"))
1230             {
1231                /* it's a {{description}} block */
1232                if (mode >= MODE_DESCRIPTION)
1233                {
1234                   /* {{description}} is a singleton and only {{settings}} may proceed it
1235                    */
1236                   fclose(fp);
1237                   log_error(LOG_LEVEL_FATAL,
1238                      "can't load actions file '%s': line %lu: {{description}} must only appear once, and only a {{settings}} block may be above it.",
1239                      csp->config->actions_file[fileid], linenum);
1240                }
1241                mode = MODE_DESCRIPTION;
1242             }
1243             else if (0 == strcmpic(start, "alias"))
1244             {
1245                /* it's an {{alias}} block */
1246                if (mode >= MODE_ALIAS)
1247                {
1248                   /* {{alias}} must be first thing in file, possibly after
1249                    * {{settings}} and {{description}}
1250                    *
1251                    * {{alias}} must only appear once.
1252                    *
1253                    * Note that these are new restrictions introduced in
1254                    * v2.9.10 in order to make actionsfile editing simpler.
1255                    * (Otherwise, reordering actionsfile entries without
1256                    * completely rewriting the file becomes non-trivial)
1257                    */
1258                   fclose(fp);
1259                   log_error(LOG_LEVEL_FATAL,
1260                      "can't load actions file '%s': line %lu: {{alias}} must only appear once, and it must be before all actions.",
1261                      csp->config->actions_file[fileid], linenum);
1262                }
1263                mode = MODE_ALIAS;
1264             }
1265             else
1266             {
1267                /* invalid {{something}} block */
1268                fclose(fp);
1269                log_error(LOG_LEVEL_FATAL,
1270                   "can't load actions file '%s': invalid line (%lu): {{%s}}",
1271                   csp->config->actions_file[fileid], linenum, start);
1272                return 1; /* never get here */
1273             }
1274          }
1275          else
1276          {
1277             /* It's an actions block */
1278
1279             char  actions_buf[BUFFER_SIZE];
1280             char * end;
1281
1282             /* set mode */
1283             mode    = MODE_ACTIONS;
1284
1285             /* free old action */
1286             if (cur_action)
1287             {
1288                if (!cur_action_used)
1289                {
1290                   free_action(cur_action);
1291                   free(cur_action);
1292                }
1293                cur_action = NULL;
1294             }
1295             cur_action_used = 0;
1296             cur_action = (struct action_spec *)zalloc(sizeof(*cur_action));
1297             if (cur_action == NULL)
1298             {
1299                fclose(fp);
1300                log_error(LOG_LEVEL_FATAL,
1301                   "can't load actions file '%s': out of memory",
1302                   csp->config->actions_file[fileid]);
1303                return 1; /* never get here */
1304             }
1305             init_action(cur_action);
1306
1307             /* trim { */
1308             strcpy(actions_buf, buf + 1);
1309
1310             /* check we have a trailing } and then trim it */
1311             end = actions_buf + strlen(actions_buf) - 1;
1312             if (*end != '}')
1313             {
1314                /* No closing } */
1315                fclose(fp);
1316                log_error(LOG_LEVEL_FATAL,
1317                   "can't load actions file '%s': invalid line (%lu): %s",
1318                   csp->config->actions_file[fileid], linenum, buf);
1319                return 1; /* never get here */
1320             }
1321             *end = '\0';
1322
1323             /* trim any whitespace immediately inside {} */
1324             chomp(actions_buf);
1325
1326             if (get_actions(actions_buf, alias_list, cur_action))
1327             {
1328                /* error */
1329                fclose(fp);
1330                log_error(LOG_LEVEL_FATAL,
1331                   "can't load actions file '%s': invalid line (%lu): %s",
1332                   csp->config->actions_file[fileid], linenum, buf);
1333                return 1; /* never get here */
1334             }
1335          }
1336       }
1337       else if (mode == MODE_SETTINGS)
1338       {
1339          /*
1340           * Part of the {{settings}} block.
1341           * For now only serves to check if the file's minimum Privoxy
1342           * version requirement is met, but we may want to read & check
1343           * permissions when we go multi-user.
1344           */
1345          if (!strncmp(buf, "for-privoxy-version=", 20))
1346          {
1347             char *version_string, *fields[3];
1348             int num_fields;
1349
1350             if ((version_string = strdup(buf + 20)) == NULL)
1351             {
1352                fclose(fp);
1353                log_error(LOG_LEVEL_FATAL,
1354                          "can't load actions file '%s': out of memory!",
1355                          csp->config->actions_file[fileid]);
1356                return 1; /* never get here */
1357             }
1358             
1359             num_fields = ssplit(version_string, ".", fields, 3, TRUE, FALSE);
1360
1361             if (num_fields < 1 || atoi(fields[0]) == 0)
1362             {
1363                log_error(LOG_LEVEL_ERROR,
1364                  "While loading actions file '%s': invalid line (%lu): %s",
1365                   csp->config->actions_file[fileid], linenum, buf);
1366             }
1367             else if (                      atoi(fields[0]) > VERSION_MAJOR
1368                      || (num_fields > 1 && atoi(fields[1]) > VERSION_MINOR)
1369                      || (num_fields > 2 && atoi(fields[2]) > VERSION_POINT))
1370             {
1371                fclose(fp);
1372                log_error(LOG_LEVEL_FATAL,
1373                          "Actions file '%s', line %lu requires newer Privoxy version: %s",
1374                          csp->config->actions_file[fileid], linenum, buf );
1375                return 1; /* never get here */
1376             }
1377             free(version_string);
1378          }
1379       }
1380       else if (mode == MODE_DESCRIPTION)
1381       {
1382          /*
1383           * Part of the {{description}} block.
1384           * Ignore for now.
1385           */
1386       }
1387       else if (mode == MODE_ALIAS)
1388       {
1389          /*
1390           * define an alias
1391           */
1392          char  actions_buf[BUFFER_SIZE];
1393          struct action_alias * new_alias;
1394
1395          char * start = strchr(buf, '=');
1396          char * end = start;
1397
1398          if ((start == NULL) || (start == buf))
1399          {
1400             log_error(LOG_LEVEL_FATAL,
1401                "can't load actions file '%s': invalid alias line (%lu): %s",
1402                csp->config->actions_file[fileid], linenum, buf);
1403             return 1; /* never get here */
1404          }
1405
1406          if ((new_alias = zalloc(sizeof(*new_alias))) == NULL)
1407          {
1408             fclose(fp);
1409             log_error(LOG_LEVEL_FATAL,
1410                "can't load actions file '%s': out of memory!",
1411                csp->config->actions_file[fileid]);
1412             return 1; /* never get here */
1413          }
1414
1415          /* Eat any the whitespace before the '=' */
1416          end--;
1417          while ((*end == ' ') || (*end == '\t'))
1418          {
1419             /*
1420              * we already know we must have at least 1 non-ws char
1421              * at start of buf - no need to check
1422              */
1423             end--;
1424          }
1425          end[1] = '\0';
1426
1427          /* Eat any the whitespace after the '=' */
1428          start++;
1429          while ((*start == ' ') || (*start == '\t'))
1430          {
1431             start++;
1432          }
1433          if (*start == '\0')
1434          {
1435             log_error(LOG_LEVEL_FATAL,
1436                "can't load actions file '%s': invalid alias line (%lu): %s",
1437                csp->config->actions_file[fileid], linenum, buf);
1438             return 1; /* never get here */
1439          }
1440
1441          if ((new_alias->name = strdup(buf)) == NULL)
1442          {
1443             fclose(fp);
1444             log_error(LOG_LEVEL_FATAL,
1445                "can't load actions file '%s': out of memory!",
1446                csp->config->actions_file[fileid]);
1447             return 1; /* never get here */
1448          }
1449
1450          strcpy(actions_buf, start);
1451
1452          if (get_actions(actions_buf, alias_list, new_alias->action))
1453          {
1454             /* error */
1455             fclose(fp);
1456             log_error(LOG_LEVEL_FATAL,
1457                "can't load actions file '%s': invalid alias line (%lu): %s = %s",
1458                csp->config->actions_file[fileid], linenum, buf, start);
1459             return 1; /* never get here */
1460          }
1461
1462          /* add to list */
1463          new_alias->next = alias_list;
1464          alias_list = new_alias;
1465       }
1466       else if (mode == MODE_ACTIONS)
1467       {
1468          /* it's a URL pattern */
1469
1470          /* allocate a new node */
1471          if ((perm = zalloc(sizeof(*perm))) == NULL)
1472          {
1473             fclose(fp);
1474             log_error(LOG_LEVEL_FATAL,
1475                "can't load actions file '%s': out of memory!",
1476                csp->config->actions_file[fileid]);
1477             return 1; /* never get here */
1478          }
1479
1480          /* Save flags */
1481          copy_action (perm->action, cur_action);
1482
1483          /* Save the URL pattern */
1484          if (create_url_spec(perm->url, buf))
1485          {
1486             fclose(fp);
1487             log_error(LOG_LEVEL_FATAL,
1488                "can't load actions file '%s': line %lu: cannot create URL pattern from: %s",
1489                csp->config->actions_file[fileid], linenum, buf);
1490             return 1; /* never get here */
1491          }
1492
1493          /* add it to the list */
1494          last_perm->next = perm;
1495          last_perm = perm;
1496       }
1497       else if (mode == MODE_START_OF_FILE)
1498       {
1499          /* oops - please have a {} line as 1st line in file. */
1500          fclose(fp);
1501          log_error(LOG_LEVEL_FATAL,
1502             "can't load actions file '%s': first needed line (%lu) is invalid: %s",
1503             csp->config->actions_file[fileid], linenum, buf);
1504          return 1; /* never get here */
1505       }
1506       else
1507       {
1508          /* How did we get here? This is impossible! */
1509          fclose(fp);
1510          log_error(LOG_LEVEL_FATAL,
1511             "can't load actions file '%s': INTERNAL ERROR - mode = %d",
1512             csp->config->actions_file[fileid], mode);
1513          return 1; /* never get here */
1514       }
1515    }
1516
1517    fclose(fp);
1518
1519    free_action(cur_action);
1520    freez(cur_action);
1521
1522    free_alias_list(alias_list);
1523
1524    /* the old one is now obsolete */
1525    if (current_actions_file[fileid])
1526    {
1527       current_actions_file[fileid]->unloader = unload_actions_file;
1528    }
1529
1530    fs->next    = files->next;
1531    files->next = fs;
1532    current_actions_file[fileid] = fs;
1533
1534    csp->actions_list[fileid] = fs;
1535
1536    return(0);
1537
1538 }
1539
1540
1541 /*********************************************************************
1542  *
1543  * Function    :  actions_to_text
1544  *
1545  * Description :  Converts a actionsfile entry from the internal
1546  *                structure into a text line.  The output is split
1547  *                into one line for each action with line continuation. 
1548  *
1549  * Parameters  :
1550  *          1  :  action = The action to format.
1551  *
1552  * Returns     :  A string.  Caller must free it.
1553  *                NULL on out-of-memory error.
1554  *
1555  *********************************************************************/
1556 char * actions_to_text(struct action_spec *action)
1557 {
1558    unsigned mask = action->mask;
1559    unsigned add  = action->add;
1560    char * result = strdup("");
1561    struct list_entry * lst;
1562
1563    /* sanity - prevents "-feature +feature" */
1564    mask |= add;
1565
1566
1567 #define DEFINE_ACTION_BOOL(__name, __bit)          \
1568    if (!(mask & __bit))                            \
1569    {                                               \
1570       string_append(&result, " -" __name " \\\n"); \
1571    }                                               \
1572    else if (add & __bit)                           \
1573    {                                               \
1574       string_append(&result, " +" __name " \\\n"); \
1575    }
1576
1577 #define DEFINE_ACTION_STRING(__name, __bit, __index)   \
1578    if (!(mask & __bit))                                \
1579    {                                                   \
1580       string_append(&result, " -" __name " \\\n");     \
1581    }                                                   \
1582    else if (add & __bit)                               \
1583    {                                                   \
1584       string_append(&result, " +" __name "{");         \
1585       string_append(&result, action->string[__index]); \
1586       string_append(&result, "} \\\n");                \
1587    }
1588
1589 #define DEFINE_ACTION_MULTI(__name, __index)         \
1590    if (action->multi_remove_all[__index])            \
1591    {                                                 \
1592       string_append(&result, " -" __name " \\\n");   \
1593    }                                                 \
1594    else                                              \
1595    {                                                 \
1596       lst = action->multi_remove[__index]->first;    \
1597       while (lst)                                    \
1598       {                                              \
1599          string_append(&result, " -" __name "{");    \
1600          string_append(&result, lst->str);           \
1601          string_append(&result, "} \\\n");           \
1602          lst = lst->next;                            \
1603       }                                              \
1604    }                                                 \
1605    lst = action->multi_add[__index]->first;          \
1606    while (lst)                                       \
1607    {                                                 \
1608       string_append(&result, " +" __name "{");       \
1609       string_append(&result, lst->str);              \
1610       string_append(&result, "} \\\n");              \
1611       lst = lst->next;                               \
1612    }
1613
1614 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
1615
1616 #include "actionlist.h"
1617
1618 #undef DEFINE_ACTION_MULTI
1619 #undef DEFINE_ACTION_STRING
1620 #undef DEFINE_ACTION_BOOL
1621 #undef DEFINE_ACTION_ALIAS
1622
1623    return result;
1624 }
1625
1626
1627 /*********************************************************************
1628  *
1629  * Function    :  actions_to_html
1630  *
1631  * Description :  Converts a actionsfile entry from numeric form
1632  *                ("mask" and "add") to a <br>-seperated HTML string
1633  *                in which each action is linked to its chapter in
1634  *                the user manual.
1635  *
1636  * Parameters  :
1637  *          1  :  csp    = Client state (for config)
1638  *          2  :  action = Action spec to be converted
1639  *
1640  * Returns     :  A string.  Caller must free it.
1641  *                NULL on out-of-memory error.
1642  *
1643  *********************************************************************/
1644 char * actions_to_html(struct client_state *csp,
1645                        struct action_spec *action)
1646 {
1647    unsigned mask = action->mask;
1648    unsigned add  = action->add;
1649    char * result = strdup("");
1650    struct list_entry * lst;
1651
1652    /* sanity - prevents "-feature +feature" */
1653    mask |= add;
1654
1655
1656 #define DEFINE_ACTION_BOOL(__name, __bit)       \
1657    if (!(mask & __bit))                         \
1658    {                                            \
1659       string_append(&result, "\n<br>-");        \
1660       string_join(&result, add_help_link(__name, csp->config)); \
1661    }                                            \
1662    else if (add & __bit)                        \
1663    {                                            \
1664       string_append(&result, "\n<br>+");        \
1665       string_join(&result, add_help_link(__name, csp->config)); \
1666    }
1667
1668 #define DEFINE_ACTION_STRING(__name, __bit, __index) \
1669    if (!(mask & __bit))                              \
1670    {                                                 \
1671       string_append(&result, "\n<br>-");             \
1672       string_join(&result, add_help_link(__name, csp->config)); \
1673    }                                                 \
1674    else if (add & __bit)                             \
1675    {                                                 \
1676       string_append(&result, "\n<br>+");             \
1677       string_join(&result, add_help_link(__name, csp->config)); \
1678       string_append(&result, "{");                   \
1679       string_join(&result, html_encode(action->string[__index])); \
1680       string_append(&result, "}");                   \
1681    }
1682
1683 #define DEFINE_ACTION_MULTI(__name, __index)          \
1684    if (action->multi_remove_all[__index])             \
1685    {                                                  \
1686       string_append(&result, "\n<br>-");              \
1687       string_join(&result, add_help_link(__name, csp->config)); \
1688    }                                                  \
1689    else                                               \
1690    {                                                  \
1691       lst = action->multi_remove[__index]->first;     \
1692       while (lst)                                     \
1693       {                                               \
1694          string_append(&result, "\n<br>-");           \
1695          string_join(&result, add_help_link(__name, csp->config)); \
1696          string_append(&result, "{");                 \
1697          string_join(&result, html_encode(lst->str)); \
1698          string_append(&result, "}");                 \
1699          lst = lst->next;                             \
1700       }                                               \
1701    }                                                  \
1702    lst = action->multi_add[__index]->first;           \
1703    while (lst)                                        \
1704    {                                                  \
1705       string_append(&result, "\n<br>+");              \
1706       string_join(&result, add_help_link(__name, csp->config)); \
1707       string_append(&result, "{");                    \
1708       string_join(&result, html_encode(lst->str));    \
1709       string_append(&result, "}");                    \
1710       lst = lst->next;                                \
1711    }
1712
1713 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
1714
1715 #include "actionlist.h"
1716
1717 #undef DEFINE_ACTION_MULTI
1718 #undef DEFINE_ACTION_STRING
1719 #undef DEFINE_ACTION_BOOL
1720 #undef DEFINE_ACTION_ALIAS
1721
1722    /* trim leading <br> */
1723    if (result && *result)
1724    {
1725       char * s = result;
1726       result = strdup(result + 5);
1727       free(s);
1728    }
1729
1730    return result;
1731 }
1732
1733
1734 /*********************************************************************
1735  *
1736  * Function    :  current_actions_to_html
1737  *
1738  * Description :  Converts a curren action spec to a <br> seperated HTML
1739  *                text in which each action is linked to its chapter in
1740  *                the user manual.
1741  *
1742  * Parameters  :
1743  *          1  :  csp    = Client state (for config) 
1744  *          2  :  action = Current action spec to be converted
1745  *
1746  * Returns     :  A string.  Caller must free it.
1747  *                NULL on out-of-memory error.
1748  *
1749  *********************************************************************/
1750 char *current_action_to_html(struct client_state *csp,
1751                              struct current_action_spec *action)
1752 {
1753    unsigned long flags  = action->flags;
1754    char * result = strdup("");
1755    struct list_entry * lst;
1756
1757 #define DEFINE_ACTION_BOOL(__name, __bit)  \
1758    if (flags & __bit)                      \
1759    {                                       \
1760       string_append(&result, "\n<br>+");   \
1761       string_join(&result, add_help_link(__name, csp->config)); \
1762    }                                       \
1763    else                                    \
1764    {                                       \
1765       string_append(&result, "\n<br>-");   \
1766       string_join(&result, add_help_link(__name, csp->config)); \
1767    }
1768
1769 #define DEFINE_ACTION_STRING(__name, __bit, __index)   \
1770    if (flags & __bit)                                  \
1771    {                                                   \
1772       string_append(&result, "\n<br>+");               \
1773       string_join(&result, add_help_link(__name, csp->config)); \
1774       string_append(&result, "{");                     \
1775       string_join(&result, html_encode(action->string[__index])); \
1776       string_append(&result, "}");                     \
1777    }                                                   \
1778    else                                                \
1779    {                                                   \
1780       string_append(&result, "\n<br>-");               \
1781       string_join(&result, add_help_link(__name, csp->config)); \
1782    }
1783
1784 #define DEFINE_ACTION_MULTI(__name, __index)           \
1785    lst = action->multi[__index]->first;                \
1786    if (lst == NULL)                                    \
1787    {                                                   \
1788       string_append(&result, "\n<br>-");              \
1789       string_join(&result, add_help_link(__name, csp->config)); \
1790    }                                                   \
1791    else                                                \
1792    {                                                   \
1793       while (lst)                                      \
1794       {                                                \
1795          string_append(&result, "\n<br>+");            \
1796          string_join(&result, add_help_link(__name, csp->config)); \
1797          string_append(&result, "{");                  \
1798          string_join(&result, html_encode(lst->str));  \
1799          string_append(&result, "}");                  \
1800          lst = lst->next;                              \
1801       }                                                \
1802    }
1803
1804 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
1805
1806 #include "actionlist.h"
1807
1808 #undef DEFINE_ACTION_MULTI
1809 #undef DEFINE_ACTION_STRING
1810 #undef DEFINE_ACTION_BOOL
1811 #undef DEFINE_ACTION_ALIAS
1812
1813    return result;
1814 }