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