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