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