In load_one_actions_file(), use an enum for the mode.
[privoxy.git] / actions.c
1 const char actions_rcs[] = "$Id: actions.c,v 1.61 2011/01/09 12:00:19 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[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    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(buf, sizeof(buf), fp, &linenum) != 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[BUFFER_SIZE];
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             /* trim { */
1253             strlcpy(actions_buf, buf + 1, sizeof(actions_buf));
1254
1255             /* check we have a trailing } and then trim it */
1256             end = actions_buf + strlen(actions_buf) - 1;
1257             if (*end != '}')
1258             {
1259                /* No closing } */
1260                fclose(fp);
1261                log_error(LOG_LEVEL_FATAL,
1262                   "can't load actions file '%s': invalid line (%lu): %s",
1263                   csp->config->actions_file[fileid], linenum, buf);
1264                return 1; /* never get here */
1265             }
1266             *end = '\0';
1267
1268             /* trim any whitespace immediately inside {} */
1269             chomp(actions_buf);
1270
1271             if (get_actions(actions_buf, alias_list, cur_action))
1272             {
1273                /* error */
1274                fclose(fp);
1275                log_error(LOG_LEVEL_FATAL,
1276                   "can't load actions file '%s': invalid line (%lu): %s",
1277                   csp->config->actions_file[fileid], linenum, buf);
1278                return 1; /* never get here */
1279             }
1280          }
1281       }
1282       else if (mode == MODE_SETTINGS)
1283       {
1284          /*
1285           * Part of the {{settings}} block.
1286           * For now only serves to check if the file's minimum Privoxy
1287           * version requirement is met, but we may want to read & check
1288           * permissions when we go multi-user.
1289           */
1290          if (!strncmp(buf, "for-privoxy-version=", 20))
1291          {
1292             char *version_string, *fields[3];
1293             int num_fields;
1294
1295             if ((version_string = strdup(buf + 20)) == NULL)
1296             {
1297                fclose(fp);
1298                log_error(LOG_LEVEL_FATAL,
1299                          "can't load actions file '%s': out of memory!",
1300                          csp->config->actions_file[fileid]);
1301                return 1; /* never get here */
1302             }
1303             
1304             num_fields = ssplit(version_string, ".", fields, 3, TRUE, FALSE);
1305
1306             if (num_fields < 1 || atoi(fields[0]) == 0)
1307             {
1308                log_error(LOG_LEVEL_ERROR,
1309                  "While loading actions file '%s': invalid line (%lu): %s",
1310                   csp->config->actions_file[fileid], linenum, buf);
1311             }
1312             else if (                      atoi(fields[0]) > VERSION_MAJOR
1313                      || (num_fields > 1 && atoi(fields[1]) > VERSION_MINOR)
1314                      || (num_fields > 2 && atoi(fields[2]) > VERSION_POINT))
1315             {
1316                fclose(fp);
1317                log_error(LOG_LEVEL_FATAL,
1318                          "Actions file '%s', line %lu requires newer Privoxy version: %s",
1319                          csp->config->actions_file[fileid], linenum, buf );
1320                return 1; /* never get here */
1321             }
1322             free(version_string);
1323          }
1324       }
1325       else if (mode == MODE_DESCRIPTION)
1326       {
1327          /*
1328           * Part of the {{description}} block.
1329           * Ignore for now.
1330           */
1331       }
1332       else if (mode == MODE_ALIAS)
1333       {
1334          /*
1335           * define an alias
1336           */
1337          char  actions_buf[BUFFER_SIZE];
1338          struct action_alias * new_alias;
1339
1340          char * start = strchr(buf, '=');
1341          char * end = start;
1342
1343          if ((start == NULL) || (start == buf))
1344          {
1345             log_error(LOG_LEVEL_FATAL,
1346                "can't load actions file '%s': invalid alias line (%lu): %s",
1347                csp->config->actions_file[fileid], linenum, buf);
1348             return 1; /* never get here */
1349          }
1350
1351          if ((new_alias = zalloc(sizeof(*new_alias))) == NULL)
1352          {
1353             fclose(fp);
1354             log_error(LOG_LEVEL_FATAL,
1355                "can't load actions file '%s': out of memory!",
1356                csp->config->actions_file[fileid]);
1357             return 1; /* never get here */
1358          }
1359
1360          /* Eat any the whitespace before the '=' */
1361          end--;
1362          while ((*end == ' ') || (*end == '\t'))
1363          {
1364             /*
1365              * we already know we must have at least 1 non-ws char
1366              * at start of buf - no need to check
1367              */
1368             end--;
1369          }
1370          end[1] = '\0';
1371
1372          /* Eat any the whitespace after the '=' */
1373          start++;
1374          while ((*start == ' ') || (*start == '\t'))
1375          {
1376             start++;
1377          }
1378          if (*start == '\0')
1379          {
1380             log_error(LOG_LEVEL_FATAL,
1381                "can't load actions file '%s': invalid alias line (%lu): %s",
1382                csp->config->actions_file[fileid], linenum, buf);
1383             return 1; /* never get here */
1384          }
1385
1386          if ((new_alias->name = strdup(buf)) == NULL)
1387          {
1388             fclose(fp);
1389             log_error(LOG_LEVEL_FATAL,
1390                "can't load actions file '%s': out of memory!",
1391                csp->config->actions_file[fileid]);
1392             return 1; /* never get here */
1393          }
1394
1395          strlcpy(actions_buf, start, sizeof(actions_buf));
1396
1397          if (get_actions(actions_buf, alias_list, new_alias->action))
1398          {
1399             /* error */
1400             fclose(fp);
1401             log_error(LOG_LEVEL_FATAL,
1402                "can't load actions file '%s': invalid alias line (%lu): %s = %s",
1403                csp->config->actions_file[fileid], linenum, buf, start);
1404             return 1; /* never get here */
1405          }
1406
1407          /* add to list */
1408          new_alias->next = alias_list;
1409          alias_list = new_alias;
1410       }
1411       else if (mode == MODE_ACTIONS)
1412       {
1413          /* it's an URL pattern */
1414
1415          /* allocate a new node */
1416          if ((perm = zalloc(sizeof(*perm))) == NULL)
1417          {
1418             fclose(fp);
1419             log_error(LOG_LEVEL_FATAL,
1420                "can't load actions file '%s': out of memory!",
1421                csp->config->actions_file[fileid]);
1422             return 1; /* never get here */
1423          }
1424
1425          perm->action = cur_action;
1426          cur_action_used = 1;
1427
1428          /* Save the URL pattern */
1429          if (create_url_spec(perm->url, buf))
1430          {
1431             fclose(fp);
1432             log_error(LOG_LEVEL_FATAL,
1433                "can't load actions file '%s': line %lu: cannot create URL pattern from: %s",
1434                csp->config->actions_file[fileid], linenum, buf);
1435             return 1; /* never get here */
1436          }
1437
1438          /* add it to the list */
1439          last_perm->next = perm;
1440          last_perm = perm;
1441       }
1442       else if (mode == MODE_START_OF_FILE)
1443       {
1444          /* oops - please have a {} line as 1st line in file. */
1445          fclose(fp);
1446          log_error(LOG_LEVEL_FATAL,
1447             "can't load actions file '%s': first needed line (%lu) is invalid: %s",
1448             csp->config->actions_file[fileid], linenum, buf);
1449          return 1; /* never get here */
1450       }
1451       else
1452       {
1453          /* How did we get here? This is impossible! */
1454          fclose(fp);
1455          log_error(LOG_LEVEL_FATAL,
1456             "can't load actions file '%s': INTERNAL ERROR - mode = %d",
1457             csp->config->actions_file[fileid], mode);
1458          return 1; /* never get here */
1459       }
1460    }
1461
1462    fclose(fp);
1463
1464    if (!cur_action_used)
1465    {
1466       free_action_spec(cur_action);
1467    }
1468    free_alias_list(alias_list);
1469
1470    /* the old one is now obsolete */
1471    if (current_actions_file[fileid])
1472    {
1473       current_actions_file[fileid]->unloader = unload_actions_file;
1474    }
1475
1476    fs->next    = files->next;
1477    files->next = fs;
1478    current_actions_file[fileid] = fs;
1479
1480    csp->actions_list[fileid] = fs;
1481
1482    return(0);
1483
1484 }
1485
1486
1487 /*********************************************************************
1488  *
1489  * Function    :  actions_to_text
1490  *
1491  * Description :  Converts a actionsfile entry from the internal
1492  *                structure into a text line.  The output is split
1493  *                into one line for each action with line continuation. 
1494  *
1495  * Parameters  :
1496  *          1  :  action = The action to format.
1497  *
1498  * Returns     :  A string.  Caller must free it.
1499  *                NULL on out-of-memory error.
1500  *
1501  *********************************************************************/
1502 char * actions_to_text(const struct action_spec *action)
1503 {
1504    unsigned long mask = action->mask;
1505    unsigned long add  = action->add;
1506    char *result = strdup("");
1507    struct list_entry * lst;
1508
1509    /* sanity - prevents "-feature +feature" */
1510    mask |= add;
1511
1512
1513 #define DEFINE_ACTION_BOOL(__name, __bit)          \
1514    if (!(mask & __bit))                            \
1515    {                                               \
1516       string_append(&result, " -" __name " \\\n"); \
1517    }                                               \
1518    else if (add & __bit)                           \
1519    {                                               \
1520       string_append(&result, " +" __name " \\\n"); \
1521    }
1522
1523 #define DEFINE_ACTION_STRING(__name, __bit, __index)   \
1524    if (!(mask & __bit))                                \
1525    {                                                   \
1526       string_append(&result, " -" __name " \\\n");     \
1527    }                                                   \
1528    else if (add & __bit)                               \
1529    {                                                   \
1530       string_append(&result, " +" __name "{");         \
1531       string_append(&result, action->string[__index]); \
1532       string_append(&result, "} \\\n");                \
1533    }
1534
1535 #define DEFINE_ACTION_MULTI(__name, __index)         \
1536    if (action->multi_remove_all[__index])            \
1537    {                                                 \
1538       string_append(&result, " -" __name " \\\n");   \
1539    }                                                 \
1540    else                                              \
1541    {                                                 \
1542       lst = action->multi_remove[__index]->first;    \
1543       while (lst)                                    \
1544       {                                              \
1545          string_append(&result, " -" __name "{");    \
1546          string_append(&result, lst->str);           \
1547          string_append(&result, "} \\\n");           \
1548          lst = lst->next;                            \
1549       }                                              \
1550    }                                                 \
1551    lst = action->multi_add[__index]->first;          \
1552    while (lst)                                       \
1553    {                                                 \
1554       string_append(&result, " +" __name "{");       \
1555       string_append(&result, lst->str);              \
1556       string_append(&result, "} \\\n");              \
1557       lst = lst->next;                               \
1558    }
1559
1560 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
1561
1562 #include "actionlist.h"
1563
1564 #undef DEFINE_ACTION_MULTI
1565 #undef DEFINE_ACTION_STRING
1566 #undef DEFINE_ACTION_BOOL
1567 #undef DEFINE_ACTION_ALIAS
1568
1569    return result;
1570 }
1571
1572
1573 /*********************************************************************
1574  *
1575  * Function    :  actions_to_html
1576  *
1577  * Description :  Converts a actionsfile entry from numeric form
1578  *                ("mask" and "add") to a <br>-seperated HTML string
1579  *                in which each action is linked to its chapter in
1580  *                the user manual.
1581  *
1582  * Parameters  :
1583  *          1  :  csp    = Client state (for config)
1584  *          2  :  action = Action spec to be converted
1585  *
1586  * Returns     :  A string.  Caller must free it.
1587  *                NULL on out-of-memory error.
1588  *
1589  *********************************************************************/
1590 char * actions_to_html(const struct client_state *csp,
1591                        const struct action_spec *action)
1592 {
1593    unsigned long mask = action->mask;
1594    unsigned long add  = action->add;
1595    char *result = strdup("");
1596    struct list_entry * lst;
1597
1598    /* sanity - prevents "-feature +feature" */
1599    mask |= add;
1600
1601
1602 #define DEFINE_ACTION_BOOL(__name, __bit)       \
1603    if (!(mask & __bit))                         \
1604    {                                            \
1605       string_append(&result, "\n<br>-");        \
1606       string_join(&result, add_help_link(__name, csp->config)); \
1607    }                                            \
1608    else if (add & __bit)                        \
1609    {                                            \
1610       string_append(&result, "\n<br>+");        \
1611       string_join(&result, add_help_link(__name, csp->config)); \
1612    }
1613
1614 #define DEFINE_ACTION_STRING(__name, __bit, __index) \
1615    if (!(mask & __bit))                              \
1616    {                                                 \
1617       string_append(&result, "\n<br>-");             \
1618       string_join(&result, add_help_link(__name, csp->config)); \
1619    }                                                 \
1620    else if (add & __bit)                             \
1621    {                                                 \
1622       string_append(&result, "\n<br>+");             \
1623       string_join(&result, add_help_link(__name, csp->config)); \
1624       string_append(&result, "{");                   \
1625       string_join(&result, html_encode(action->string[__index])); \
1626       string_append(&result, "}");                   \
1627    }
1628
1629 #define DEFINE_ACTION_MULTI(__name, __index)          \
1630    if (action->multi_remove_all[__index])             \
1631    {                                                  \
1632       string_append(&result, "\n<br>-");              \
1633       string_join(&result, add_help_link(__name, csp->config)); \
1634    }                                                  \
1635    else                                               \
1636    {                                                  \
1637       lst = action->multi_remove[__index]->first;     \
1638       while (lst)                                     \
1639       {                                               \
1640          string_append(&result, "\n<br>-");           \
1641          string_join(&result, add_help_link(__name, csp->config)); \
1642          string_append(&result, "{");                 \
1643          string_join(&result, html_encode(lst->str)); \
1644          string_append(&result, "}");                 \
1645          lst = lst->next;                             \
1646       }                                               \
1647    }                                                  \
1648    lst = action->multi_add[__index]->first;           \
1649    while (lst)                                        \
1650    {                                                  \
1651       string_append(&result, "\n<br>+");              \
1652       string_join(&result, add_help_link(__name, csp->config)); \
1653       string_append(&result, "{");                    \
1654       string_join(&result, html_encode(lst->str));    \
1655       string_append(&result, "}");                    \
1656       lst = lst->next;                                \
1657    }
1658
1659 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
1660
1661 #include "actionlist.h"
1662
1663 #undef DEFINE_ACTION_MULTI
1664 #undef DEFINE_ACTION_STRING
1665 #undef DEFINE_ACTION_BOOL
1666 #undef DEFINE_ACTION_ALIAS
1667
1668    /* trim leading <br> */
1669    if (result && *result)
1670    {
1671       char * s = result;
1672       result = strdup(result + 5);
1673       free(s);
1674    }
1675
1676    return result;
1677 }
1678
1679
1680 /*********************************************************************
1681  *
1682  * Function    :  current_actions_to_html
1683  *
1684  * Description :  Converts a curren action spec to a <br> seperated HTML
1685  *                text in which each action is linked to its chapter in
1686  *                the user manual.
1687  *
1688  * Parameters  :
1689  *          1  :  csp    = Client state (for config) 
1690  *          2  :  action = Current action spec to be converted
1691  *
1692  * Returns     :  A string.  Caller must free it.
1693  *                NULL on out-of-memory error.
1694  *
1695  *********************************************************************/
1696 char *current_action_to_html(const struct client_state *csp,
1697                              const struct current_action_spec *action)
1698 {
1699    unsigned long flags  = action->flags;
1700    struct list_entry * lst;
1701    char *result   = strdup("");
1702    char *active   = strdup("");
1703    char *inactive = strdup("");
1704
1705 #define DEFINE_ACTION_BOOL(__name, __bit)  \
1706    if (flags & __bit)                      \
1707    {                                       \
1708       string_append(&active, "\n<br>+");   \
1709       string_join(&active, add_help_link(__name, csp->config)); \
1710    }                                       \
1711    else                                    \
1712    {                                       \
1713       string_append(&inactive, "\n<br>-"); \
1714       string_join(&inactive, add_help_link(__name, csp->config)); \
1715    }
1716
1717 #define DEFINE_ACTION_STRING(__name, __bit, __index)   \
1718    if (flags & __bit)                                  \
1719    {                                                   \
1720       string_append(&active, "\n<br>+");               \
1721       string_join(&active, add_help_link(__name, csp->config)); \
1722       string_append(&active, "{");                     \
1723       string_join(&active, html_encode(action->string[__index])); \
1724       string_append(&active, "}");                     \
1725    }                                                   \
1726    else                                                \
1727    {                                                   \
1728       string_append(&inactive, "\n<br>-");             \
1729       string_join(&inactive, add_help_link(__name, csp->config)); \
1730    }
1731
1732 #define DEFINE_ACTION_MULTI(__name, __index)           \
1733    lst = action->multi[__index]->first;                \
1734    if (lst == NULL)                                    \
1735    {                                                   \
1736       string_append(&inactive, "\n<br>-");             \
1737       string_join(&inactive, add_help_link(__name, csp->config)); \
1738    }                                                   \
1739    else                                                \
1740    {                                                   \
1741       while (lst)                                      \
1742       {                                                \
1743          string_append(&active, "\n<br>+");            \
1744          string_join(&active, add_help_link(__name, csp->config)); \
1745          string_append(&active, "{");                  \
1746          string_join(&active, html_encode(lst->str));  \
1747          string_append(&active, "}");                  \
1748          lst = lst->next;                              \
1749       }                                                \
1750    }
1751
1752 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
1753
1754 #include "actionlist.h"
1755
1756 #undef DEFINE_ACTION_MULTI
1757 #undef DEFINE_ACTION_STRING
1758 #undef DEFINE_ACTION_BOOL
1759 #undef DEFINE_ACTION_ALIAS
1760
1761    if (active != NULL)
1762    {
1763       string_append(&result, active);
1764       freez(active);
1765    }
1766    string_append(&result, "\n<br>");
1767    if (inactive != NULL)
1768    {
1769       string_append(&result, inactive);
1770       freez(inactive);
1771    }
1772    return result;
1773 }