In a fatal error message in load_one_actions_file(), cover both URL and TAG patterns
[privoxy.git] / actions.c
1 const char actions_rcs[] = "$Id: actions.c,v 1.66 2011/03/03 14:39:57 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-2011 the
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    enum {
1059       MODE_START_OF_FILE = 1,
1060       MODE_SETTINGS      = 2,
1061       MODE_DESCRIPTION   = 3,
1062       MODE_ALIAS         = 4,
1063       MODE_ACTIONS       = 5
1064    } mode;
1065
1066    FILE *fp;
1067    struct url_actions *last_perm;
1068    struct url_actions *perm;
1069    char  *buf;
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    mode = MODE_START_OF_FILE;
1076
1077    if (!check_file_changed(current_actions_file[fileid], csp->config->actions_file[fileid], &fs))
1078    {
1079       /* No need to load */
1080       csp->actions_list[fileid] = current_actions_file[fileid];
1081       return 0;
1082    }
1083    if (!fs)
1084    {
1085       log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': %E. "
1086          "Note that beginning with Privoxy 3.0.7, actions files have to be specified "
1087          "with their complete file names.", csp->config->actions_file[fileid]);
1088       return 1; /* never get here */
1089    }
1090
1091    fs->f = last_perm = (struct url_actions *)zalloc(sizeof(*last_perm));
1092    if (last_perm == NULL)
1093    {
1094       log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': out of memory!",
1095                 csp->config->actions_file[fileid]);
1096       return 1; /* never get here */
1097    }
1098
1099    if ((fp = fopen(csp->config->actions_file[fileid], "r")) == NULL)
1100    {
1101       log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': error opening file: %E",
1102                 csp->config->actions_file[fileid]);
1103       return 1; /* never get here */
1104    }
1105
1106    log_error(LOG_LEVEL_INFO, "Loading actions file: %s", csp->config->actions_file[fileid]);
1107
1108    while (read_config_line(fp, &linenum, &buf) != NULL)
1109    {
1110       if (*buf == '{')
1111       {
1112          /* It's a header block */
1113          if (buf[1] == '{')
1114          {
1115             /* It's {{settings}} or {{alias}} */
1116             size_t len = strlen(buf);
1117             char * start = buf + 2;
1118             char * end = buf + len - 1;
1119             if ((len < (size_t)5) || (*end-- != '}') || (*end-- != '}'))
1120             {
1121                /* too short */
1122                fclose(fp);
1123                log_error(LOG_LEVEL_FATAL,
1124                   "can't load actions file '%s': invalid line (%lu): %s", 
1125                   csp->config->actions_file[fileid], linenum, buf);
1126                return 1; /* never get here */
1127             }
1128
1129             /* Trim leading and trailing whitespace. */
1130             end[1] = '\0';
1131             chomp(start);
1132
1133             if (*start == '\0')
1134             {
1135                /* too short */
1136                fclose(fp);
1137                log_error(LOG_LEVEL_FATAL,
1138                   "can't load actions file '%s': invalid line (%lu): {{ }}",
1139                   csp->config->actions_file[fileid], linenum);
1140                return 1; /* never get here */
1141             }
1142
1143             /*
1144              * An actionsfile can optionally contain the following blocks.
1145              * They *MUST* be in this order, to simplify processing:
1146              *
1147              * {{settings}}
1148              * name=value...
1149              *
1150              * {{description}}
1151              * ...free text, format TBD, but no line may start with a '{'...
1152              *
1153              * {{alias}}
1154              * name=actions...
1155              *
1156              * The actual actions must be *after* these special blocks.
1157              * None of these special blocks may be repeated.
1158              *
1159              */
1160             if (0 == strcmpic(start, "settings"))
1161             {
1162                /* it's a {{settings}} block */
1163                if (mode >= MODE_SETTINGS)
1164                {
1165                   /* {{settings}} must be first thing in file and must only
1166                    * appear once.
1167                    */
1168                   fclose(fp);
1169                   log_error(LOG_LEVEL_FATAL,
1170                      "can't load actions file '%s': line %lu: {{settings}} must only appear once, and it must be before anything else.",
1171                      csp->config->actions_file[fileid], linenum);
1172                }
1173                mode = MODE_SETTINGS;
1174             }
1175             else if (0 == strcmpic(start, "description"))
1176             {
1177                /* it's a {{description}} block */
1178                if (mode >= MODE_DESCRIPTION)
1179                {
1180                   /* {{description}} is a singleton and only {{settings}} may proceed it
1181                    */
1182                   fclose(fp);
1183                   log_error(LOG_LEVEL_FATAL,
1184                      "can't load actions file '%s': line %lu: {{description}} must only appear once, and only a {{settings}} block may be above it.",
1185                      csp->config->actions_file[fileid], linenum);
1186                }
1187                mode = MODE_DESCRIPTION;
1188             }
1189             else if (0 == strcmpic(start, "alias"))
1190             {
1191                /* it's an {{alias}} block */
1192                if (mode >= MODE_ALIAS)
1193                {
1194                   /* {{alias}} must be first thing in file, possibly after
1195                    * {{settings}} and {{description}}
1196                    *
1197                    * {{alias}} must only appear once.
1198                    *
1199                    * Note that these are new restrictions introduced in
1200                    * v2.9.10 in order to make actionsfile editing simpler.
1201                    * (Otherwise, reordering actionsfile entries without
1202                    * completely rewriting the file becomes non-trivial)
1203                    */
1204                   fclose(fp);
1205                   log_error(LOG_LEVEL_FATAL,
1206                      "can't load actions file '%s': line %lu: {{alias}} must only appear once, and it must be before all actions.",
1207                      csp->config->actions_file[fileid], linenum);
1208                }
1209                mode = MODE_ALIAS;
1210             }
1211             else
1212             {
1213                /* invalid {{something}} block */
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, start);
1218                return 1; /* never get here */
1219             }
1220          }
1221          else
1222          {
1223             /* It's an actions block */
1224
1225             char *actions_buf;
1226             char * end;
1227
1228             /* set mode */
1229             mode = MODE_ACTIONS;
1230
1231             /* free old action */
1232             if (cur_action)
1233             {
1234                if (!cur_action_used)
1235                {
1236                   free_action_spec(cur_action);
1237                }
1238                cur_action = NULL;
1239             }
1240             cur_action_used = 0;
1241             cur_action = (struct action_spec *)zalloc(sizeof(*cur_action));
1242             if (cur_action == NULL)
1243             {
1244                fclose(fp);
1245                log_error(LOG_LEVEL_FATAL,
1246                   "can't load actions file '%s': out of memory",
1247                   csp->config->actions_file[fileid]);
1248                return 1; /* never get here */
1249             }
1250             init_action(cur_action);
1251
1252             /*
1253              * Copy the buffer before messing with it as we may need the
1254              * unmodified version in for the fatal error messages. Given
1255              * that this is not a common event, we could instead simply
1256              * read the line again.
1257              *
1258              * buf + 1 to skip the leading '{'
1259              */
1260             actions_buf = strdup(buf + 1);
1261             if (actions_buf == NULL)
1262             {
1263                fclose(fp);
1264                log_error(LOG_LEVEL_FATAL,
1265                   "can't load actions file '%s': out of memory",
1266                   csp->config->actions_file[fileid]);
1267                return 1; /* never get here */
1268             }
1269
1270             /* check we have a trailing } and then trim it */
1271             end = actions_buf + strlen(actions_buf) - 1;
1272             if (*end != '}')
1273             {
1274                /* No closing } */
1275                fclose(fp);
1276                freez(actions_buf);
1277                log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': "
1278                   "Missing trailing '}' in action section starting at line (%lu): %s",
1279                   csp->config->actions_file[fileid], linenum, buf);
1280                return 1; /* never get here */
1281             }
1282             *end = '\0';
1283
1284             /* trim any whitespace immediately inside {} */
1285             chomp(actions_buf);
1286
1287             if (get_actions(actions_buf, alias_list, cur_action))
1288             {
1289                /* error */
1290                fclose(fp);
1291                freez(actions_buf);
1292                log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': "
1293                   "can't completely parse the action section starting at line (%lu): %s",
1294                   csp->config->actions_file[fileid], linenum, buf);
1295                return 1; /* never get here */
1296             }
1297             freez(actions_buf);
1298          }
1299       }
1300       else if (mode == MODE_SETTINGS)
1301       {
1302          /*
1303           * Part of the {{settings}} block.
1304           * For now only serves to check if the file's minimum Privoxy
1305           * version requirement is met, but we may want to read & check
1306           * permissions when we go multi-user.
1307           */
1308          if (!strncmp(buf, "for-privoxy-version=", 20))
1309          {
1310             char *version_string, *fields[3];
1311             int num_fields;
1312
1313             if ((version_string = strdup(buf + 20)) == NULL)
1314             {
1315                fclose(fp);
1316                log_error(LOG_LEVEL_FATAL,
1317                          "can't load actions file '%s': out of memory!",
1318                          csp->config->actions_file[fileid]);
1319                return 1; /* never get here */
1320             }
1321             
1322             num_fields = ssplit(version_string, ".", fields, 3, TRUE, FALSE);
1323
1324             if (num_fields < 1 || atoi(fields[0]) == 0)
1325             {
1326                log_error(LOG_LEVEL_ERROR,
1327                  "While loading actions file '%s': invalid line (%lu): %s",
1328                   csp->config->actions_file[fileid], linenum, buf);
1329             }
1330             else if (                      atoi(fields[0]) > VERSION_MAJOR
1331                      || (num_fields > 1 && atoi(fields[1]) > VERSION_MINOR)
1332                      || (num_fields > 2 && atoi(fields[2]) > VERSION_POINT))
1333             {
1334                fclose(fp);
1335                log_error(LOG_LEVEL_FATAL,
1336                          "Actions file '%s', line %lu requires newer Privoxy version: %s",
1337                          csp->config->actions_file[fileid], linenum, buf );
1338                return 1; /* never get here */
1339             }
1340             free(version_string);
1341          }
1342       }
1343       else if (mode == MODE_DESCRIPTION)
1344       {
1345          /*
1346           * Part of the {{description}} block.
1347           * Ignore for now.
1348           */
1349       }
1350       else if (mode == MODE_ALIAS)
1351       {
1352          /*
1353           * define an alias
1354           */
1355          char  actions_buf[BUFFER_SIZE];
1356          struct action_alias * new_alias;
1357
1358          char * start = strchr(buf, '=');
1359          char * end = start;
1360
1361          if ((start == NULL) || (start == buf))
1362          {
1363             log_error(LOG_LEVEL_FATAL,
1364                "can't load actions file '%s': invalid alias line (%lu): %s",
1365                csp->config->actions_file[fileid], linenum, buf);
1366             return 1; /* never get here */
1367          }
1368
1369          if ((new_alias = zalloc(sizeof(*new_alias))) == NULL)
1370          {
1371             fclose(fp);
1372             log_error(LOG_LEVEL_FATAL,
1373                "can't load actions file '%s': out of memory!",
1374                csp->config->actions_file[fileid]);
1375             return 1; /* never get here */
1376          }
1377
1378          /* Eat any the whitespace before the '=' */
1379          end--;
1380          while ((*end == ' ') || (*end == '\t'))
1381          {
1382             /*
1383              * we already know we must have at least 1 non-ws char
1384              * at start of buf - no need to check
1385              */
1386             end--;
1387          }
1388          end[1] = '\0';
1389
1390          /* Eat any the whitespace after the '=' */
1391          start++;
1392          while ((*start == ' ') || (*start == '\t'))
1393          {
1394             start++;
1395          }
1396          if (*start == '\0')
1397          {
1398             log_error(LOG_LEVEL_FATAL,
1399                "can't load actions file '%s': invalid alias line (%lu): %s",
1400                csp->config->actions_file[fileid], linenum, buf);
1401             return 1; /* never get here */
1402          }
1403
1404          if ((new_alias->name = strdup(buf)) == NULL)
1405          {
1406             fclose(fp);
1407             log_error(LOG_LEVEL_FATAL,
1408                "can't load actions file '%s': out of memory!",
1409                csp->config->actions_file[fileid]);
1410             return 1; /* never get here */
1411          }
1412
1413          strlcpy(actions_buf, start, sizeof(actions_buf));
1414
1415          if (get_actions(actions_buf, alias_list, new_alias->action))
1416          {
1417             /* error */
1418             fclose(fp);
1419             log_error(LOG_LEVEL_FATAL,
1420                "can't load actions file '%s': invalid alias line (%lu): %s = %s",
1421                csp->config->actions_file[fileid], linenum, buf, start);
1422             return 1; /* never get here */
1423          }
1424
1425          /* add to list */
1426          new_alias->next = alias_list;
1427          alias_list = new_alias;
1428       }
1429       else if (mode == MODE_ACTIONS)
1430       {
1431          /* it's an URL pattern */
1432
1433          /* allocate a new node */
1434          if ((perm = zalloc(sizeof(*perm))) == NULL)
1435          {
1436             fclose(fp);
1437             log_error(LOG_LEVEL_FATAL,
1438                "can't load actions file '%s': out of memory!",
1439                csp->config->actions_file[fileid]);
1440             return 1; /* never get here */
1441          }
1442
1443          perm->action = cur_action;
1444          cur_action_used = 1;
1445
1446          /* Save the URL pattern */
1447          if (create_url_spec(perm->url, buf))
1448          {
1449             fclose(fp);
1450             log_error(LOG_LEVEL_FATAL,
1451                "can't load actions file '%s': line %lu: cannot create URL or TAG pattern from: %s",
1452                csp->config->actions_file[fileid], linenum, buf);
1453             return 1; /* never get here */
1454          }
1455
1456          /* add it to the list */
1457          last_perm->next = perm;
1458          last_perm = perm;
1459       }
1460       else if (mode == MODE_START_OF_FILE)
1461       {
1462          /* oops - please have a {} line as 1st line in file. */
1463          fclose(fp);
1464          log_error(LOG_LEVEL_FATAL,
1465             "can't load actions file '%s': first needed line (%lu) is invalid: %s",
1466             csp->config->actions_file[fileid], linenum, buf);
1467          return 1; /* never get here */
1468       }
1469       else
1470       {
1471          /* How did we get here? This is impossible! */
1472          fclose(fp);
1473          log_error(LOG_LEVEL_FATAL,
1474             "can't load actions file '%s': INTERNAL ERROR - mode = %d",
1475             csp->config->actions_file[fileid], mode);
1476          return 1; /* never get here */
1477       }
1478       freez(buf);
1479    }
1480
1481    fclose(fp);
1482
1483    if (!cur_action_used)
1484    {
1485       free_action_spec(cur_action);
1486    }
1487    free_alias_list(alias_list);
1488
1489    /* the old one is now obsolete */
1490    if (current_actions_file[fileid])
1491    {
1492       current_actions_file[fileid]->unloader = unload_actions_file;
1493    }
1494
1495    fs->next    = files->next;
1496    files->next = fs;
1497    current_actions_file[fileid] = fs;
1498
1499    csp->actions_list[fileid] = fs;
1500
1501    return(0);
1502
1503 }
1504
1505
1506 /*********************************************************************
1507  *
1508  * Function    :  actions_to_text
1509  *
1510  * Description :  Converts a actionsfile entry from the internal
1511  *                structure into a text line.  The output is split
1512  *                into one line for each action with line continuation. 
1513  *
1514  * Parameters  :
1515  *          1  :  action = The action to format.
1516  *
1517  * Returns     :  A string.  Caller must free it.
1518  *                NULL on out-of-memory error.
1519  *
1520  *********************************************************************/
1521 char * actions_to_text(const struct action_spec *action)
1522 {
1523    unsigned long mask = action->mask;
1524    unsigned long add  = action->add;
1525    char *result = strdup("");
1526    struct list_entry * lst;
1527
1528    /* sanity - prevents "-feature +feature" */
1529    mask |= add;
1530
1531
1532 #define DEFINE_ACTION_BOOL(__name, __bit)          \
1533    if (!(mask & __bit))                            \
1534    {                                               \
1535       string_append(&result, " -" __name " \\\n"); \
1536    }                                               \
1537    else if (add & __bit)                           \
1538    {                                               \
1539       string_append(&result, " +" __name " \\\n"); \
1540    }
1541
1542 #define DEFINE_ACTION_STRING(__name, __bit, __index)   \
1543    if (!(mask & __bit))                                \
1544    {                                                   \
1545       string_append(&result, " -" __name " \\\n");     \
1546    }                                                   \
1547    else if (add & __bit)                               \
1548    {                                                   \
1549       string_append(&result, " +" __name "{");         \
1550       string_append(&result, action->string[__index]); \
1551       string_append(&result, "} \\\n");                \
1552    }
1553
1554 #define DEFINE_ACTION_MULTI(__name, __index)         \
1555    if (action->multi_remove_all[__index])            \
1556    {                                                 \
1557       string_append(&result, " -" __name " \\\n");   \
1558    }                                                 \
1559    else                                              \
1560    {                                                 \
1561       lst = action->multi_remove[__index]->first;    \
1562       while (lst)                                    \
1563       {                                              \
1564          string_append(&result, " -" __name "{");    \
1565          string_append(&result, lst->str);           \
1566          string_append(&result, "} \\\n");           \
1567          lst = lst->next;                            \
1568       }                                              \
1569    }                                                 \
1570    lst = action->multi_add[__index]->first;          \
1571    while (lst)                                       \
1572    {                                                 \
1573       string_append(&result, " +" __name "{");       \
1574       string_append(&result, lst->str);              \
1575       string_append(&result, "} \\\n");              \
1576       lst = lst->next;                               \
1577    }
1578
1579 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
1580
1581 #include "actionlist.h"
1582
1583 #undef DEFINE_ACTION_MULTI
1584 #undef DEFINE_ACTION_STRING
1585 #undef DEFINE_ACTION_BOOL
1586 #undef DEFINE_ACTION_ALIAS
1587
1588    return result;
1589 }
1590
1591
1592 /*********************************************************************
1593  *
1594  * Function    :  actions_to_html
1595  *
1596  * Description :  Converts a actionsfile entry from numeric form
1597  *                ("mask" and "add") to a <br>-seperated HTML string
1598  *                in which each action is linked to its chapter in
1599  *                the user manual.
1600  *
1601  * Parameters  :
1602  *          1  :  csp    = Client state (for config)
1603  *          2  :  action = Action spec to be converted
1604  *
1605  * Returns     :  A string.  Caller must free it.
1606  *                NULL on out-of-memory error.
1607  *
1608  *********************************************************************/
1609 char * actions_to_html(const struct client_state *csp,
1610                        const struct action_spec *action)
1611 {
1612    unsigned long mask = action->mask;
1613    unsigned long add  = action->add;
1614    char *result = strdup("");
1615    struct list_entry * lst;
1616
1617    /* sanity - prevents "-feature +feature" */
1618    mask |= add;
1619
1620
1621 #define DEFINE_ACTION_BOOL(__name, __bit)       \
1622    if (!(mask & __bit))                         \
1623    {                                            \
1624       string_append(&result, "\n<br>-");        \
1625       string_join(&result, add_help_link(__name, csp->config)); \
1626    }                                            \
1627    else if (add & __bit)                        \
1628    {                                            \
1629       string_append(&result, "\n<br>+");        \
1630       string_join(&result, add_help_link(__name, csp->config)); \
1631    }
1632
1633 #define DEFINE_ACTION_STRING(__name, __bit, __index) \
1634    if (!(mask & __bit))                              \
1635    {                                                 \
1636       string_append(&result, "\n<br>-");             \
1637       string_join(&result, add_help_link(__name, csp->config)); \
1638    }                                                 \
1639    else if (add & __bit)                             \
1640    {                                                 \
1641       string_append(&result, "\n<br>+");             \
1642       string_join(&result, add_help_link(__name, csp->config)); \
1643       string_append(&result, "{");                   \
1644       string_join(&result, html_encode(action->string[__index])); \
1645       string_append(&result, "}");                   \
1646    }
1647
1648 #define DEFINE_ACTION_MULTI(__name, __index)          \
1649    if (action->multi_remove_all[__index])             \
1650    {                                                  \
1651       string_append(&result, "\n<br>-");              \
1652       string_join(&result, add_help_link(__name, csp->config)); \
1653    }                                                  \
1654    else                                               \
1655    {                                                  \
1656       lst = action->multi_remove[__index]->first;     \
1657       while (lst)                                     \
1658       {                                               \
1659          string_append(&result, "\n<br>-");           \
1660          string_join(&result, add_help_link(__name, csp->config)); \
1661          string_append(&result, "{");                 \
1662          string_join(&result, html_encode(lst->str)); \
1663          string_append(&result, "}");                 \
1664          lst = lst->next;                             \
1665       }                                               \
1666    }                                                  \
1667    lst = action->multi_add[__index]->first;           \
1668    while (lst)                                        \
1669    {                                                  \
1670       string_append(&result, "\n<br>+");              \
1671       string_join(&result, add_help_link(__name, csp->config)); \
1672       string_append(&result, "{");                    \
1673       string_join(&result, html_encode(lst->str));    \
1674       string_append(&result, "}");                    \
1675       lst = lst->next;                                \
1676    }
1677
1678 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
1679
1680 #include "actionlist.h"
1681
1682 #undef DEFINE_ACTION_MULTI
1683 #undef DEFINE_ACTION_STRING
1684 #undef DEFINE_ACTION_BOOL
1685 #undef DEFINE_ACTION_ALIAS
1686
1687    /* trim leading <br> */
1688    if (result && *result)
1689    {
1690       char * s = result;
1691       result = strdup(result + 5);
1692       free(s);
1693    }
1694
1695    return result;
1696 }
1697
1698
1699 /*********************************************************************
1700  *
1701  * Function    :  current_actions_to_html
1702  *
1703  * Description :  Converts a curren action spec to a <br> seperated HTML
1704  *                text in which each action is linked to its chapter in
1705  *                the user manual.
1706  *
1707  * Parameters  :
1708  *          1  :  csp    = Client state (for config) 
1709  *          2  :  action = Current action spec to be converted
1710  *
1711  * Returns     :  A string.  Caller must free it.
1712  *                NULL on out-of-memory error.
1713  *
1714  *********************************************************************/
1715 char *current_action_to_html(const struct client_state *csp,
1716                              const struct current_action_spec *action)
1717 {
1718    unsigned long flags  = action->flags;
1719    struct list_entry * lst;
1720    char *result   = strdup("");
1721    char *active   = strdup("");
1722    char *inactive = strdup("");
1723
1724 #define DEFINE_ACTION_BOOL(__name, __bit)  \
1725    if (flags & __bit)                      \
1726    {                                       \
1727       string_append(&active, "\n<br>+");   \
1728       string_join(&active, add_help_link(__name, csp->config)); \
1729    }                                       \
1730    else                                    \
1731    {                                       \
1732       string_append(&inactive, "\n<br>-"); \
1733       string_join(&inactive, add_help_link(__name, csp->config)); \
1734    }
1735
1736 #define DEFINE_ACTION_STRING(__name, __bit, __index)   \
1737    if (flags & __bit)                                  \
1738    {                                                   \
1739       string_append(&active, "\n<br>+");               \
1740       string_join(&active, add_help_link(__name, csp->config)); \
1741       string_append(&active, "{");                     \
1742       string_join(&active, html_encode(action->string[__index])); \
1743       string_append(&active, "}");                     \
1744    }                                                   \
1745    else                                                \
1746    {                                                   \
1747       string_append(&inactive, "\n<br>-");             \
1748       string_join(&inactive, add_help_link(__name, csp->config)); \
1749    }
1750
1751 #define DEFINE_ACTION_MULTI(__name, __index)           \
1752    lst = action->multi[__index]->first;                \
1753    if (lst == NULL)                                    \
1754    {                                                   \
1755       string_append(&inactive, "\n<br>-");             \
1756       string_join(&inactive, add_help_link(__name, csp->config)); \
1757    }                                                   \
1758    else                                                \
1759    {                                                   \
1760       while (lst)                                      \
1761       {                                                \
1762          string_append(&active, "\n<br>+");            \
1763          string_join(&active, add_help_link(__name, csp->config)); \
1764          string_append(&active, "{");                  \
1765          string_join(&active, html_encode(lst->str));  \
1766          string_append(&active, "}");                  \
1767          lst = lst->next;                              \
1768       }                                                \
1769    }
1770
1771 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
1772
1773 #include "actionlist.h"
1774
1775 #undef DEFINE_ACTION_MULTI
1776 #undef DEFINE_ACTION_STRING
1777 #undef DEFINE_ACTION_BOOL
1778 #undef DEFINE_ACTION_ALIAS
1779
1780    if (active != NULL)
1781    {
1782       string_append(&result, active);
1783       freez(active);
1784    }
1785    string_append(&result, "\n<br>");
1786    if (inactive != NULL)
1787    {
1788       string_append(&result, inactive);
1789       freez(inactive);
1790    }
1791    return result;
1792 }