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