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