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