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