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