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