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