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