Fixed Win32 error logging in bind_port.
[privoxy.git] / actions.c
1 const char actions_rcs[] = "$Id: actions.c,v 1.32.2.1 2002/05/26 12:13:16 roro Exp $";
2 /*********************************************************************
3  *
4  * File        :  $Source: /cvsroot/ijbswa/current/Attic/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.32.2.1  2002/05/26 12:13:16  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    int i;
882
883    for (i = 0; i < MAX_ACTION_FILES; i++)
884    {
885       if (current_actions_file[i])
886       {
887          current_actions_file[i]->unloader = unload_actions_file;
888          current_actions_file[i] = NULL;
889       }
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  *
990  * Function    :  load_one_actions_file
991  *
992  * Description :  Read and parse a action file and add to files
993  *                list.
994  *
995  * Parameters  :
996  *          1  :  csp = Current client state (buffers, headers, etc...)
997  *          2  :  fileid = File index to load.
998  *
999  * Returns     :  0 => Ok, everything else is an error.
1000  *
1001  *********************************************************************/
1002 static int load_one_actions_file(struct client_state *csp, int fileid)
1003 {
1004
1005    /*
1006     * Parser mode.
1007     * Note: Keep these in the order they occur in the file, they are
1008     * sometimes tested with <=
1009     */
1010 #define MODE_START_OF_FILE 1
1011 #define MODE_SETTINGS      2
1012 #define MODE_DESCRIPTION   3
1013 #define MODE_ALIAS         4
1014 #define MODE_ACTIONS       5
1015
1016    int mode = MODE_START_OF_FILE;
1017
1018    FILE *fp;
1019    struct url_actions *last_perm;
1020    struct url_actions *perm;
1021    char  buf[BUFFER_SIZE];
1022    struct file_list *fs;
1023    struct action_spec * cur_action = NULL;
1024    int cur_action_used = 0;
1025    struct action_alias * alias_list = NULL;
1026    unsigned long linenum = 0;
1027
1028    if (!check_file_changed(current_actions_file[fileid], csp->config->actions_file[fileid], &fs))
1029    {
1030       /* No need to load */
1031       csp->actions_list[fileid] = current_actions_file[fileid];
1032       return 0;
1033    }
1034    if (!fs)
1035    {
1036       log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': error finding file: %E",
1037                 csp->config->actions_file[fileid]);
1038       return 1; /* never get here */
1039    }
1040
1041    fs->f = last_perm = (struct url_actions *)zalloc(sizeof(*last_perm));
1042    if (last_perm == NULL)
1043    {
1044       log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': out of memory!",
1045                 csp->config->actions_file[fileid]);
1046       return 1; /* never get here */
1047    }
1048
1049    if ((fp = fopen(csp->config->actions_file[fileid], "r")) == NULL)
1050    {
1051       log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': error opening file: %E",
1052                 csp->config->actions_file[fileid]);
1053       return 1; /* never get here */
1054    }
1055
1056    while (read_config_line(buf, sizeof(buf), fp, &linenum) != NULL)
1057    {
1058       if (*buf == '{')
1059       {
1060          /* It's a header block */
1061          if (buf[1] == '{')
1062          {
1063             /* It's {{settings}} or {{alias}} */
1064             int len = strlen(buf);
1065             char * start = buf + 2;
1066             char * end = buf + len - 1;
1067             if ((len < 5) || (*end-- != '}') || (*end-- != '}'))
1068             {
1069                /* too short */
1070                fclose(fp);
1071                log_error(LOG_LEVEL_FATAL,
1072                   "can't load actions file '%s': invalid line (%lu): %s", 
1073                   csp->config->actions_file[fileid], linenum, buf);
1074                return 1; /* never get here */
1075             }
1076
1077             /* Trim leading and trailing whitespace. */
1078             end[1] = '\0';
1079             chomp(start);
1080
1081             if (*start == '\0')
1082             {
1083                /* too short */
1084                fclose(fp);
1085                log_error(LOG_LEVEL_FATAL,
1086                   "can't load actions file '%s': invalid line (%lu): {{ }}",
1087                   csp->config->actions_file[fileid], linenum);
1088                return 1; /* never get here */
1089             }
1090
1091             /*
1092              * An actionsfile can optionally contain the following blocks.
1093              * They *MUST* be in this order, to simplify processing:
1094              *
1095              * {{settings}}
1096              * name=value...
1097              *
1098              * {{description}}
1099              * ...free text, format TBD, but no line may start with a '{'...
1100              *
1101              * {{alias}}
1102              * name=actions...
1103              *
1104              * The actual actions must be *after* these special blocks.
1105              * None of these special blocks may be repeated.
1106              *
1107              */
1108             if (0 == strcmpic(start, "settings"))
1109             {
1110                /* it's a {{settings}} block */
1111                if (mode >= MODE_SETTINGS)
1112                {
1113                   /* {{settings}} must be first thing in file and must only
1114                    * appear once.
1115                    */
1116                   fclose(fp);
1117                   log_error(LOG_LEVEL_FATAL,
1118                      "can't load actions file '%s': line %lu: {{settings}} must only appear once, and it must be before anything else.",
1119                      csp->config->actions_file[fileid], linenum);
1120                }
1121                mode = MODE_SETTINGS;
1122             }
1123             else if (0 == strcmpic(start, "description"))
1124             {
1125                /* it's a {{description}} block */
1126                if (mode >= MODE_DESCRIPTION)
1127                {
1128                   /* {{description}} is a singleton and only {{settings}} may proceed it
1129                    */
1130                   fclose(fp);
1131                   log_error(LOG_LEVEL_FATAL,
1132                      "can't load actions file '%s': line %lu: {{description}} must only appear once, and only a {{settings}} block may be above it.",
1133                      csp->config->actions_file[fileid], linenum);
1134                }
1135                mode = MODE_DESCRIPTION;
1136             }
1137             else if (0 == strcmpic(start, "alias"))
1138             {
1139                /* it's an {{alias}} block */
1140                if (mode >= MODE_ALIAS)
1141                {
1142                   /* {{alias}} must be first thing in file, possibly after
1143                    * {{settings}} and {{description}}
1144                    *
1145                    * {{alias}} must only appear once.
1146                    *
1147                    * Note that these are new restrictions introduced in
1148                    * v2.9.10 in order to make actionsfile editing simpler.
1149                    * (Otherwise, reordering actionsfile entries without
1150                    * completely rewriting the file becomes non-trivial)
1151                    */
1152                   fclose(fp);
1153                   log_error(LOG_LEVEL_FATAL,
1154                      "can't load actions file '%s': line %lu: {{alias}} must only appear once, and it must be before all actions.",
1155                      csp->config->actions_file[fileid], linenum);
1156                }
1157                mode = MODE_ALIAS;
1158             }
1159             else
1160             {
1161                /* invalid {{something}} block */
1162                fclose(fp);
1163                log_error(LOG_LEVEL_FATAL,
1164                   "can't load actions file '%s': invalid line (%lu): {{%s}}",
1165                   csp->config->actions_file[fileid], linenum, start);
1166                return 1; /* never get here */
1167             }
1168          }
1169          else
1170          {
1171             /* It's an actions block */
1172
1173             char  actions_buf[BUFFER_SIZE];
1174             char * end;
1175
1176             /* set mode */
1177             mode    = MODE_ACTIONS;
1178
1179             /* free old action */
1180             if (cur_action)
1181             {
1182                if (!cur_action_used)
1183                {
1184                   free_action(cur_action);
1185                   free(cur_action);
1186                }
1187                cur_action = NULL;
1188             }
1189             cur_action_used = 0;
1190             cur_action = (struct action_spec *)zalloc(sizeof(*cur_action));
1191             if (cur_action == NULL)
1192             {
1193                fclose(fp);
1194                log_error(LOG_LEVEL_FATAL,
1195                   "can't load actions file '%s': out of memory",
1196                   csp->config->actions_file[fileid]);
1197                return 1; /* never get here */
1198             }
1199             init_action(cur_action);
1200
1201             /* trim { */
1202             strcpy(actions_buf, buf + 1);
1203
1204             /* check we have a trailing } and then trim it */
1205             end = actions_buf + strlen(actions_buf) - 1;
1206             if (*end != '}')
1207             {
1208                /* No closing } */
1209                fclose(fp);
1210                log_error(LOG_LEVEL_FATAL,
1211                   "can't load actions file '%s': invalid line (%lu): %s",
1212                   csp->config->actions_file[fileid], linenum, buf);
1213                return 1; /* never get here */
1214             }
1215             *end = '\0';
1216
1217             /* trim any whitespace immediately inside {} */
1218             chomp(actions_buf);
1219
1220             if (get_actions(actions_buf, alias_list, cur_action))
1221             {
1222                /* error */
1223                fclose(fp);
1224                log_error(LOG_LEVEL_FATAL,
1225                   "can't load actions file '%s': invalid line (%lu): %s",
1226                   csp->config->actions_file[fileid], linenum, buf);
1227                return 1; /* never get here */
1228             }
1229          }
1230       }
1231       else if (mode == MODE_SETTINGS)
1232       {
1233          /*
1234           * Part of the {{settings}} block.
1235           * Ignore for now, but we may want to read & check permissions
1236           * when we go multi-user.
1237           */
1238       }
1239       else if (mode == MODE_DESCRIPTION)
1240       {
1241          /*
1242           * Part of the {{description}} block.
1243           * Ignore for now.
1244           */
1245       }
1246       else if (mode == MODE_ALIAS)
1247       {
1248          /*
1249           * define an alias
1250           */
1251          char  actions_buf[BUFFER_SIZE];
1252          struct action_alias * new_alias;
1253
1254          char * start = strchr(buf, '=');
1255          char * end = start;
1256
1257          if ((start == NULL) || (start == buf))
1258          {
1259             log_error(LOG_LEVEL_FATAL,
1260                "can't load actions file '%s': invalid alias line (%lu): %s",
1261                csp->config->actions_file[fileid], linenum, buf);
1262             return 1; /* never get here */
1263          }
1264
1265          if ((new_alias = zalloc(sizeof(*new_alias))) == NULL)
1266          {
1267             fclose(fp);
1268             log_error(LOG_LEVEL_FATAL,
1269                "can't load actions file '%s': out of memory!",
1270                csp->config->actions_file[fileid]);
1271             return 1; /* never get here */
1272          }
1273
1274          /* Eat any the whitespace before the '=' */
1275          end--;
1276          while ((*end == ' ') || (*end == '\t'))
1277          {
1278             /*
1279              * we already know we must have at least 1 non-ws char
1280              * at start of buf - no need to check
1281              */
1282             end--;
1283          }
1284          end[1] = '\0';
1285
1286          /* Eat any the whitespace after the '=' */
1287          start++;
1288          while ((*start == ' ') || (*start == '\t'))
1289          {
1290             start++;
1291          }
1292          if (*start == '\0')
1293          {
1294             log_error(LOG_LEVEL_FATAL,
1295                "can't load actions file '%s': invalid alias line (%lu): %s",
1296                csp->config->actions_file[fileid], linenum, buf);
1297             return 1; /* never get here */
1298          }
1299
1300          new_alias->name = strdup(buf);
1301
1302          strcpy(actions_buf, start);
1303
1304          if (get_actions(actions_buf, alias_list, new_alias->action))
1305          {
1306             /* error */
1307             fclose(fp);
1308             log_error(LOG_LEVEL_FATAL,
1309                "can't load actions file '%s': invalid alias line (%lu): %s = %s",
1310                csp->config->actions_file[fileid], linenum, buf, start);
1311             return 1; /* never get here */
1312          }
1313
1314          /* add to list */
1315          new_alias->next = alias_list;
1316          alias_list = new_alias;
1317       }
1318       else if (mode == MODE_ACTIONS)
1319       {
1320          /* it's a URL pattern */
1321
1322          /* allocate a new node */
1323          if ((perm = zalloc(sizeof(*perm))) == NULL)
1324          {
1325             fclose(fp);
1326             log_error(LOG_LEVEL_FATAL,
1327                "can't load actions file '%s': out of memory!",
1328                csp->config->actions_file[fileid]);
1329             return 1; /* never get here */
1330          }
1331
1332          /* Save flags */
1333          copy_action (perm->action, cur_action);
1334
1335          /* Save the URL pattern */
1336          if (create_url_spec(perm->url, buf))
1337          {
1338             fclose(fp);
1339             log_error(LOG_LEVEL_FATAL,
1340                "can't load actions file '%s': line %lu: cannot create URL pattern from: %s",
1341                csp->config->actions_file[fileid], linenum, buf);
1342             return 1; /* never get here */
1343          }
1344
1345          /* add it to the list */
1346          last_perm->next = perm;
1347          last_perm = perm;
1348       }
1349       else if (mode == MODE_START_OF_FILE)
1350       {
1351          /* oops - please have a {} line as 1st line in file. */
1352          fclose(fp);
1353          log_error(LOG_LEVEL_FATAL,
1354             "can't load actions file '%s': first needed line (%lu) is invalid: %s",
1355             csp->config->actions_file[fileid], linenum, buf);
1356          return 1; /* never get here */
1357       }
1358       else
1359       {
1360          /* How did we get here? This is impossible! */
1361          fclose(fp);
1362          log_error(LOG_LEVEL_FATAL,
1363             "can't load actions file '%s': INTERNAL ERROR - mode = %d",
1364             csp->config->actions_file[fileid], mode);
1365          return 1; /* never get here */
1366       }
1367    }
1368
1369    fclose(fp);
1370
1371    free_action(cur_action);
1372
1373    free_alias_list(alias_list);
1374
1375    /* the old one is now obsolete */
1376    if (current_actions_file[fileid])
1377    {
1378       current_actions_file[fileid]->unloader = unload_actions_file;
1379    }
1380
1381    fs->next    = files->next;
1382    files->next = fs;
1383    current_actions_file[fileid] = fs;
1384
1385    csp->actions_list[fileid] = fs;
1386
1387    return(0);
1388
1389 }
1390
1391
1392 /*********************************************************************
1393  *
1394  * Function    :  actions_to_text
1395  *
1396  * Description :  Converts a actionsfile entry from the internal
1397  *                structurt into a text line.  The output is split
1398  *                into one line for each action with line continuation. 
1399  *
1400  * Parameters  :
1401  *          1  :  action = The action to format.
1402  *
1403  * Returns     :  A string.  Caller must free it.
1404  *                NULL on out-of-memory error.
1405  *
1406  *********************************************************************/
1407 char * actions_to_text(struct action_spec *action)
1408 {
1409    unsigned mask = action->mask;
1410    unsigned add  = action->add;
1411    char * result = strdup("");
1412    struct list_entry * lst;
1413
1414    /* sanity - prevents "-feature +feature" */
1415    mask |= add;
1416
1417
1418 #define DEFINE_ACTION_BOOL(__name, __bit)          \
1419    if (!(mask & __bit))                            \
1420    {                                               \
1421       string_append(&result, " -" __name " \\\n"); \
1422    }                                               \
1423    else if (add & __bit)                           \
1424    {                                               \
1425       string_append(&result, " +" __name " \\\n"); \
1426    }
1427
1428 #define DEFINE_ACTION_STRING(__name, __bit, __index)   \
1429    if (!(mask & __bit))                                \
1430    {                                                   \
1431       string_append(&result, " -" __name " \\\n");     \
1432    }                                                   \
1433    else if (add & __bit)                               \
1434    {                                                   \
1435       string_append(&result, " +" __name "{");         \
1436       string_append(&result, action->string[__index]); \
1437       string_append(&result, "} \\\n");                \
1438    }
1439
1440 #define DEFINE_ACTION_MULTI(__name, __index)         \
1441    if (action->multi_remove_all[__index])            \
1442    {                                                 \
1443       string_append(&result, " -" __name " \\\n");   \
1444    }                                                 \
1445    else                                              \
1446    {                                                 \
1447       lst = action->multi_remove[__index]->first;    \
1448       while (lst)                                    \
1449       {                                              \
1450          string_append(&result, " -" __name "{");    \
1451          string_append(&result, lst->str);           \
1452          string_append(&result, "} \\\n");           \
1453          lst = lst->next;                            \
1454       }                                              \
1455    }                                                 \
1456    lst = action->multi_add[__index]->first;          \
1457    while (lst)                                       \
1458    {                                                 \
1459       string_append(&result, " +" __name "{");       \
1460       string_append(&result, lst->str);              \
1461       string_append(&result, "} \\\n");              \
1462       lst = lst->next;                               \
1463    }
1464
1465 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
1466
1467 #include "actionlist.h"
1468
1469 #undef DEFINE_ACTION_MULTI
1470 #undef DEFINE_ACTION_STRING
1471 #undef DEFINE_ACTION_BOOL
1472 #undef DEFINE_ACTION_ALIAS
1473
1474    return result;
1475 }
1476
1477
1478 /*********************************************************************
1479  *
1480  * Function    :  actions_to_html
1481  *
1482  * Description :  Converts a actionsfile entry from numeric form
1483  *                ("mask" and "add") to a <br>-seperated HTML string
1484  *                in which each action is linked to its chapter in
1485  *                the user manual.
1486  *
1487  * Parameters  :
1488  *          1  :  csp    = Client state (for config)
1489  *          2  :  action = Action spec to be converted
1490  *
1491  * Returns     :  A string.  Caller must free it.
1492  *                NULL on out-of-memory error.
1493  *
1494  *********************************************************************/
1495 char * actions_to_html(struct client_state *csp,
1496                        struct action_spec *action)
1497 {
1498    unsigned mask = action->mask;
1499    unsigned add  = action->add;
1500    char * result = strdup("");
1501    struct list_entry * lst;
1502
1503    /* sanity - prevents "-feature +feature" */
1504    mask |= add;
1505
1506
1507 #define DEFINE_ACTION_BOOL(__name, __bit)       \
1508    if (!(mask & __bit))                         \
1509    {                                            \
1510       string_append(&result, "\n<br>-");        \
1511       string_join(&result, add_help_link(__name, csp->config)); \
1512    }                                            \
1513    else if (add & __bit)                        \
1514    {                                            \
1515       string_append(&result, "\n<br>+");        \
1516       string_join(&result, add_help_link(__name, csp->config)); \
1517    }
1518
1519 #define DEFINE_ACTION_STRING(__name, __bit, __index) \
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       string_append(&result, "{");                   \
1530       string_join(&result, html_encode(action->string[__index])); \
1531       string_append(&result, "}");                   \
1532    }
1533
1534 #define DEFINE_ACTION_MULTI(__name, __index)          \
1535    if (action->multi_remove_all[__index])             \
1536    {                                                  \
1537       string_append(&result, "\n<br>-");              \
1538       string_join(&result, add_help_link(__name, csp->config)); \
1539    }                                                  \
1540    else                                               \
1541    {                                                  \
1542       lst = action->multi_remove[__index]->first;     \
1543       while (lst)                                     \
1544       {                                               \
1545          string_append(&result, "\n<br>-");           \
1546          string_join(&result, add_help_link(__name, csp->config)); \
1547          string_append(&result, "{");                 \
1548          string_join(&result, html_encode(lst->str)); \
1549          string_append(&result, "}");                 \
1550          lst = lst->next;                             \
1551       }                                               \
1552    }                                                  \
1553    lst = action->multi_add[__index]->first;           \
1554    while (lst)                                        \
1555    {                                                  \
1556       string_append(&result, "\n<br>+");              \
1557       string_join(&result, add_help_link(__name, csp->config)); \
1558       string_append(&result, "{");                    \
1559       string_join(&result, html_encode(lst->str));    \
1560       string_append(&result, "}");                    \
1561       lst = lst->next;                                \
1562    }
1563
1564 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
1565
1566 #include "actionlist.h"
1567
1568 #undef DEFINE_ACTION_MULTI
1569 #undef DEFINE_ACTION_STRING
1570 #undef DEFINE_ACTION_BOOL
1571 #undef DEFINE_ACTION_ALIAS
1572
1573    /* trim leading <br> */
1574    if (result && *result)
1575    {
1576       char * s = result;
1577       result = strdup(result + 5);
1578       free(s);
1579    }
1580
1581    return result;
1582 }
1583
1584
1585 /*********************************************************************
1586  *
1587  * Function    :  current_actions_to_html
1588  *
1589  * Description :  Converts a curren action spec to a <br> seperated HTML
1590  *                text in which each action is linked to its chapter in
1591  *                the user manual.
1592  *
1593  * Parameters  :
1594  *          1  :  csp    = Client state (for config) 
1595  *          2  :  action = Current action spec to be converted
1596  *
1597  * Returns     :  A string.  Caller must free it.
1598  *                NULL on out-of-memory error.
1599  *
1600  *********************************************************************/
1601 char *current_action_to_html(struct client_state *csp,
1602                              struct current_action_spec *action)
1603 {
1604    unsigned long flags  = action->flags;
1605    char * result = strdup("");
1606    struct list_entry * lst;
1607
1608 #define DEFINE_ACTION_BOOL(__name, __bit)  \
1609    if (flags & __bit)                      \
1610    {                                       \
1611       string_append(&result, "\n<br>+");   \
1612       string_join(&result, add_help_link(__name, csp->config)); \
1613    }                                       \
1614    else                                    \
1615    {                                       \
1616       string_append(&result, "\n<br>-");   \
1617       string_join(&result, add_help_link(__name, csp->config)); \
1618    }
1619
1620 #define DEFINE_ACTION_STRING(__name, __bit, __index)   \
1621    if (flags & __bit)                                  \
1622    {                                                   \
1623       string_append(&result, "\n<br>+");               \
1624       string_join(&result, add_help_link(__name, csp->config)); \
1625       string_append(&result, "{");                     \
1626       string_join(&result, html_encode(action->string[__index])); \
1627       string_append(&result, "}");                     \
1628    }                                                   \
1629    else                                                \
1630    {                                                   \
1631       string_append(&result, "\n<br>-");               \
1632       string_join(&result, add_help_link(__name, csp->config)); \
1633    }
1634
1635 #define DEFINE_ACTION_MULTI(__name, __index)           \
1636    lst = action->multi[__index]->first;                \
1637    if (lst == NULL)                                    \
1638    {                                                   \
1639       string_append(&result, "\n<br>-");              \
1640       string_join(&result, add_help_link(__name, csp->config)); \
1641    }                                                   \
1642    else                                                \
1643    {                                                   \
1644       while (lst)                                      \
1645       {                                                \
1646          string_append(&result, "\n<br>+");            \
1647          string_join(&result, add_help_link(__name, csp->config)); \
1648          string_append(&result, "{");                  \
1649          string_join(&result, html_encode(lst->str));  \
1650          string_append(&result, "}");                  \
1651          lst = lst->next;                              \
1652       }                                                \
1653    }
1654
1655 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
1656
1657 #include "actionlist.h"
1658
1659 #undef DEFINE_ACTION_MULTI
1660 #undef DEFINE_ACTION_STRING
1661 #undef DEFINE_ACTION_BOOL
1662 #undef DEFINE_ACTION_ALIAS
1663
1664    return result;
1665 }