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