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