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