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