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