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