3fe17cb2718538812fc13d9ccacc4c432952b59c
[privoxy.git] / actions.c
1 const char actions_rcs[] = "$Id: actions.c,v 1.2 2001/05/31 21:40:00 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.2  2001/05/31 21:40:00  jongfoster
37  *    Removing some commented out, obsolete blocks of code.
38  *
39  *    Revision 1.1  2001/05/31 21:16:46  jongfoster
40  *    Moved functions to process the action list into this new file.
41  *
42  *
43  *********************************************************************/
44 \f
45
46 #include "config.h"
47
48 #include <stdio.h>
49 #include <string.h>
50
51 #include "project.h"
52 #include "jcc.h"
53 #include "list.h"
54 #include "actions.h"
55 #include "miscutil.h"
56 #include "errlog.h"
57 #include "loaders.h"
58
59 const char actions_h_rcs[] = ACTIONS_H_VERSION;
60
61 /* This structure is used to hold user-defined aliases */
62 struct action_alias
63 {
64    const char * name;
65    struct action_spec action[1];
66    struct action_alias * next;
67 };
68
69
70 /*
71  * Must declare this in this file for the above structure.
72  * FIXME: Make this static or put structure in header.
73  */
74 extern int get_actions (char *line, struct action_alias * alias_list,
75                         struct action_spec *cur_action);
76
77 /*
78  * We need the main list of options.
79  *
80  * First, we need a way to tell between boolean, string, and multi-string
81  * options.  For string and multistring options, we also need to be
82  * able to tell the difference between a "+" and a "-".  (For bools,
83  * the "+"/"-" information is encoded in "add" and "mask").  So we use
84  * an enumerated type (well, the preprocessor equivalent).  Here are
85  * the values:
86  */
87 #define AV_NONE       0 /* +opt -opt */
88 #define AV_ADD_STRING 1 /* +stropt{string} */
89 #define AV_REM_STRING 2 /* -stropt */
90 #define AV_ADD_MULTI  3 /* +multiopt{string} +multiopt{string2} */
91 #define AV_REM_MULTI  4 /* -multiopt{string} -multiopt{*}       */
92
93 /*
94  * We need a structure to hold the name, flag changes, 
95  * type, and string index.
96  */
97 struct action_name
98 {
99    const char * name;
100    unsigned mask;   /* a bit set to "0" = remove action */
101    unsigned add;    /* a bit set to "1" = add action */
102    int takes_value; /* an AV_... constant */
103    int index;       /* index into strings[] or multi[] */
104 };
105
106 /*
107  * And with those building blocks in place, here's the array.
108  */
109 static const struct action_name action_names[] =
110 {
111    /*
112     * Well actually there's no data here - it's in actionlist.h
113     * This keeps it together to make it easy to change.
114     *
115     * Here's the macros used to format it:
116     */
117 #define DEFINE_ACTION_MULTI(name,index)                   \
118    { "+" name, ACTION_MASK_ALL, 0, AV_ADD_MULTI, index }, \
119    { "-" name, ACTION_MASK_ALL, 0, AV_REM_MULTI, index },
120 #define DEFINE_ACTION_STRING(name,flag,index)                 \
121    { "+" name, ACTION_MASK_ALL, flag, AV_ADD_STRING, index }, \
122    { "-" name, ~flag, 0, AV_REM_STRING, index },
123 #define DEFINE_ACTION_BOOL(name,flag)   \
124    { "+" name, ACTION_MASK_ALL, flag }, \
125    { "-" name, ~flag, 0 },
126 #define DEFINE_ACTION_ALIAS 1 /* Want aliases please */
127
128 #include "actionlist.h"
129
130 #undef DEFINE_ACTION_MULTI
131 #undef DEFINE_ACTION_STRING
132 #undef DEFINE_ACTION_BOOL
133 #undef DEFINE_ACTION_ALIAS
134
135    { NULL, 0, 0 } /* End marker */
136 };
137
138
139 /*********************************************************************
140  *
141  * Function    :  merge_actions
142  *
143  * Description :  Merge two actions together.
144  *                Similar to "cur_action += new_action".
145  *
146  * Parameters  :
147  *          1  :  cur_action = Current actions, to modify.
148  *          2  :  new_action = Action to add.
149  *
150  * Returns     :  N/A
151  *
152  *********************************************************************/
153 void merge_actions (struct action_spec *dest, 
154                     const struct action_spec *src)
155 {
156    int i;
157
158    dest->mask &= src->mask;
159    dest->add  &= src->mask;
160    dest->add  |= src->add;
161
162    for (i = 0; i < ACTION_STRING_COUNT; i++)
163    {
164       char * str = src->string[i];
165       if (str)
166       {
167          freez(dest->string[i]);
168          dest->string[i] = strdup(str);
169       }
170    }
171
172    for (i = 0; i < ACTION_MULTI_COUNT; i++)
173    {
174       if (src->multi_remove_all[i])
175       {
176          /* Remove everything from dest */
177          destroy_list(dest->multi_remove[i]);
178          destroy_list(dest->multi_add[i]);
179          dest->multi_remove_all[i] = 1;
180          list_duplicate(dest->multi_add[i], src->multi_add[i]);
181       }
182       else if (dest->multi_remove_all[i])
183       {
184          /*
185           * dest already removes everything, so we only need to worry
186           * about what we add.
187           */
188          list_remove_list(dest->multi_add[i], src->multi_remove[i]);
189          list_append_list_unique(dest->multi_add[i], src->multi_add[i]);
190       }
191       else
192       {
193          /* No "remove all"s to worry about. */
194          list_remove_list(dest->multi_add[i], src->multi_remove[i]);
195          list_append_list_unique(dest->multi_remove[i], src->multi_remove[i]);
196          list_append_list_unique(dest->multi_add[i], src->multi_add[i]);
197       }
198    }
199 }
200
201
202 /*********************************************************************
203  *
204  * Function    :  copy_action
205  *
206  * Description :  Copy an action_specs.
207  *                Similar to "cur_action = new_action".
208  *
209  * Parameters  :
210  *          1  :  dest = Destination of copy.
211  *          2  :  src = Source for copy.
212  *
213  * Returns     :  N/A
214  *
215  *********************************************************************/
216 void copy_action (struct action_spec *dest, 
217                   const struct action_spec *src)
218 {
219    int i;
220
221    dest->mask = src->mask;
222    dest->add  = src->add;
223
224    for (i = 0; i < ACTION_STRING_COUNT; i++)
225    {
226       char * str = src->string[i];
227       dest->string[i] = (str ? strdup(str) : NULL);
228    }
229
230    for (i = 0; i < ACTION_MULTI_COUNT; i++)
231    {
232       dest->multi_remove_all[i] = src->multi_remove_all[i];
233       list_duplicate(dest->multi_remove[i], src->multi_remove[i]);
234       list_duplicate(dest->multi_add[i],    src->multi_add[i]);
235    }
236 }
237
238
239 /*********************************************************************
240  *
241  * Function    :  free_action
242  *
243  * Description :  Free an action_specs.
244  *
245  * Parameters  :
246  *          1  :  src = Source to free.
247  *
248  * Returns     :  N/A
249  *
250  *********************************************************************/
251 void free_action (struct action_spec *src)
252 {
253    int i;
254
255    for (i = 0; i < ACTION_STRING_COUNT; i++)
256    {
257       freez(src->string[i]);
258    }
259
260    for (i = 0; i < ACTION_MULTI_COUNT; i++)
261    {
262       destroy_list(src->multi_remove[i]);
263       destroy_list(src->multi_add[i]);
264    }
265
266    memset(src, '\0', sizeof(*src));
267 }
268
269
270 /*********************************************************************
271  *
272  * Function    :  get_action_token
273  *
274  * Description :  Parses a line for the first action.
275  *                Modifies it's input array, doesn't allocate memory.
276  *                e.g. given:
277  *                *line="  +abc{def}  -ghi "
278  *                Returns:
279  *                *line="  -ghi "
280  *                *name="+abc"
281  *                *value="def"
282  *
283  * Parameters  :
284  *          1  :  line = [in] The line containing the action.
285  *                       [out] Start of next action on line, or
286  *                       NULL if we reached the end of line before
287  *                       we found an action.
288  *          2  :  name = [out] Start of action name, null
289  *                       terminated.  NULL on EOL
290  *          3  :  value = [out] Start of action value, null 
291  *                        terminated.  NULL if none or EOL.
292  *
293  * Returns     :  0 => Ok
294  *                nonzero => Mismatched {} (line was trashed anyway)
295  *
296  *********************************************************************/
297 int get_action_token(char **line, char **name, char **value)
298 {
299    char * str = *line;
300    char ch;
301
302    /* set default returns */
303    *line = NULL;
304    *name = NULL;
305    *value = NULL;
306
307    /* Eat any leading whitespace */
308    while ((*str == ' ') || (*str == '\t'))
309    {
310       str++;
311    }
312
313    if (*str == '\0')
314    {
315       return 0;
316    }
317
318    if (*str == '{')
319    {
320       /* null name, just value is prohibited */
321       return 1;
322    }
323
324    *name = str;
325
326    /* parse option */
327    while (((ch = *str) != '\0') && 
328           (ch != ' ') && (ch != '\t') && (ch != '{'))
329    {
330       if (ch == '}')
331       {
332          /* error */
333          return 1;
334       }
335       str++;
336    }
337    *str = '\0';
338
339    if (ch != '{')
340    {
341       /* no value */
342       if (ch == '\0')
343       {
344          /* EOL - be careful not to run off buffer */
345          *line = str;
346       }
347       else
348       {
349          /* More to parse next time. */
350          *line = str + 1;
351       }
352       return 0;
353    }
354
355    str++;
356    *value = str;
357
358    str = strchr(str, '}');
359    if (str == NULL)
360    {
361       /* error */
362       *value = NULL;
363       return 1;
364    }
365
366    /* got value */
367    *str = '\0';
368    *line = str + 1;
369
370    chomp(*value);
371
372    return 0;
373 }
374
375
376 /*********************************************************************
377  *
378  * Function    :  get_actions
379  *
380  * Description :  Parses a list of actions.
381  *
382  * Parameters  :
383  *          1  :  line = The string containing the actions.
384  *                       Will be written to by this function.
385  *          2  :  aliases = Custom alias list, or NULL for none.
386  *          3  :  cur_action = Where to store the action.  Caller
387  *                             allocates memory.
388  *
389  * Returns     :  0 => Ok
390  *                nonzero => Error (line was trashed anyway)
391  *
392  *********************************************************************/
393 int get_actions(char *line, struct action_alias * alias_list,
394                 struct action_spec *cur_action)
395 {
396    memset(cur_action, '\0', sizeof(*cur_action));
397    cur_action->mask = ACTION_MASK_ALL;
398
399    while (line)
400    {
401       char * option = NULL;
402       char * value = NULL;
403
404       if (get_action_token(&line, &option, &value))
405       {
406          return 1;
407       }
408
409       if (option)
410       {
411          /* handle option in 'option' */
412       
413          /* Check for standard action name */
414          const struct action_name * action = action_names;
415
416          while ( (action->name != NULL) && (0 != strcmpic(action->name, option)) )
417          {
418             action++;
419          }
420          if (action->name != NULL)
421          {
422             /* Found it */
423             cur_action->mask &= action->mask;
424             cur_action->add  &= action->mask;
425             cur_action->add  |= action->add;
426
427             switch (action->takes_value)
428             {
429             case AV_NONE:
430                /* ignore any option. */
431                break;
432             case AV_ADD_STRING:
433                {
434                   /* add single string. */
435
436                   if ((value == NULL) || (*value == '\0'))
437                   {
438                      return 1;
439                   }
440                   /* FIXME: should validate option string here */
441                   freez (cur_action->string[action->index]);
442                   cur_action->string[action->index] = strdup(value);
443                   break;
444                }
445             case AV_REM_STRING:
446                {
447                   /* remove single string. */
448
449                   freez (cur_action->string[action->index]);
450                   break;
451                }
452             case AV_ADD_MULTI:
453                {
454                   /* append multi string. */
455
456                   struct list * remove = cur_action->multi_remove[action->index];
457                   struct list * add    = cur_action->multi_add[action->index];
458
459                   if ((value == NULL) || (*value == '\0'))
460                   {
461                      return 1;
462                   }
463
464                   list_remove_item(remove, value);
465                   enlist_unique(add, value);
466                   break;
467                }
468             case AV_REM_MULTI:
469                {
470                   /* remove multi string. */
471
472                   struct list * remove = cur_action->multi_remove[action->index];
473                   struct list * add    = cur_action->multi_add[action->index];
474
475                   if ( (value == NULL) || (*value == '\0')
476                      || ((*value == '*') && (value[1] == '\0')) )
477                   {
478                      /*
479                       * no option, or option == "*".
480                       *
481                       * Remove *ALL*.
482                       */
483                      destroy_list(remove);
484                      destroy_list(add);
485                      cur_action->multi_remove_all[action->index] = 1;
486                   }
487                   else
488                   {
489                      /* Valid option - remove only 1 option */
490
491                      if ( !cur_action->multi_remove_all[action->index] )
492                      {
493                         /* there isn't a catch-all in the remove list already */
494                         enlist_unique(remove, value);
495                      }
496                      list_remove_item(add, value);
497                   }
498                   break;
499                }
500             default:
501                /* Shouldn't get here unless there's memory corruption. */
502                return 1;
503             }
504          }
505          else
506          {
507             /* try user aliases. */
508             const struct action_alias * alias = alias_list;
509             
510             while ( (alias != NULL) && (0 != strcmpic(alias->name, option)) )
511             {
512                alias = alias->next;
513             }
514             if (alias != NULL)
515             {
516                /* Found it */
517                merge_actions(cur_action, alias->action);
518             }
519             else
520             {
521                /* Bad action name */
522                return 1;
523             }
524          }
525       }
526    }
527
528    return 0;
529 }
530
531
532 /*********************************************************************
533  *
534  * Function    :  actions_to_text
535  *
536  * Description :  Converts a actionsfile entry from numeric form
537  *                ("mask" and "add") to text.
538  *
539  * Parameters  :
540  *          1  :  mask = As from struct url_actions
541  *          2  :  add  = As from struct url_actions
542  *
543  * Returns     :  A string.  Caller must free it.
544  *
545  *********************************************************************/
546 char * actions_to_text(struct action_spec *action)
547 {
548    unsigned mask = action->mask;
549    unsigned add  = action->add;
550    char * result = strdup("");
551    struct list * lst;
552
553    /* sanity - prevents "-feature +feature" */
554    mask |= add;
555
556
557 #define DEFINE_ACTION_BOOL(__name, __bit)   \
558    if (!(mask & __bit))                     \
559    {                                        \
560       result = strsav(result, " -" __name); \
561    }                                        \
562    else if (add & __bit)                    \
563    {                                        \
564       result = strsav(result, " +" __name); \
565    }
566
567 #define DEFINE_ACTION_STRING(__name, __bit, __index) \
568    if (!(mask & __bit))                     \
569    {                                        \
570       result = strsav(result, " -" __name); \
571    }                                        \
572    else if (add & __bit)                    \
573    {                                        \
574       result = strsav(result, " +" __name "{"); \
575       result = strsav(result, action->string[__index]); \
576       result = strsav(result, "}"); \
577    }
578
579 #define DEFINE_ACTION_MULTI(__name, __index)         \
580    if (action->multi_remove_all[__index])            \
581    {                                                 \
582       result = strsav(result, " -" __name "{*}");    \
583    }                                                 \
584    else                                              \
585    {                                                 \
586       lst = action->multi_remove[__index]->next;     \
587       while (lst)                                    \
588       {                                              \
589          result = strsav(result, " -" __name "{");   \
590          result = strsav(result, lst->str);          \
591          result = strsav(result, "}");               \
592          lst = lst->next;                            \
593       }                                              \
594    }                                                 \
595    lst = action->multi_add[__index]->next;           \
596    while (lst)                                       \
597    {                                                 \
598       result = strsav(result, " +" __name "{");      \
599       result = strsav(result, lst->str);             \
600       result = strsav(result, "}");                  \
601       lst = lst->next;                               \
602    }
603
604 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
605
606 #include "actionlist.h"
607
608 #undef DEFINE_ACTION_MULTI
609 #undef DEFINE_ACTION_STRING
610 #undef DEFINE_ACTION_BOOL
611 #undef DEFINE_ACTION_ALIAS
612
613    return result;
614 }
615
616
617 /*********************************************************************
618  *
619  * Function    :  current_actions_to_text
620  *
621  * Description :  Converts a actionsfile entry to text.
622  *
623  * Parameters  :
624  *          1  :  action = Action
625  *
626  * Returns     :  A string.  Caller must free it.
627  *
628  *********************************************************************/
629 char * current_action_to_text(struct current_action_spec *action)
630 {
631    unsigned flags  = action->flags;
632    char * result = strdup("");
633    struct list * lst;
634
635 #define DEFINE_ACTION_BOOL(__name, __bit)   \
636    if (flags & __bit)                       \
637    {                                        \
638       result = strsav(result, " +" __name); \
639    }                                        \
640    else                                     \
641    {                                        \
642       result = strsav(result, " -" __name); \
643    }
644
645 #define DEFINE_ACTION_STRING(__name, __bit, __index)    \
646    if (flags & __bit)                                   \
647    {                                                    \
648       result = strsav(result, " +" __name "{");         \
649       result = strsav(result, action->string[__index]); \
650       result = strsav(result, "}");                     \
651    }                                                    \
652    else                                                 \
653    {                                                    \
654       result = strsav(result, " -" __name);             \
655    }
656
657 #define DEFINE_ACTION_MULTI(__name, __index)            \
658    lst = action->multi[__index]->next;                  \
659    if (lst == NULL)                                     \
660    {                                                    \
661       result = strsav(result, " -" __name);             \
662    }                                                    \
663    else                                                 \
664    {                                                    \
665       while (lst)                                       \
666       {                                                 \
667          result = strsav(result, " +" __name "{");      \
668          result = strsav(result, lst->str);             \
669          result = strsav(result, "}");                  \
670          lst = lst->next;                               \
671       }                                                 \
672    }
673
674 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
675
676 #include "actionlist.h"
677
678 #undef DEFINE_ACTION_MULTI
679 #undef DEFINE_ACTION_STRING
680 #undef DEFINE_ACTION_BOOL
681 #undef DEFINE_ACTION_ALIAS
682
683    return result;
684 }
685
686
687 /*********************************************************************
688  *
689  * Function    :  init_current_action
690  *
691  * Description :  Zero out an action.
692  *
693  * Parameters  :
694  *          1  :  dest = An uninitialized current_action_spec.
695  *
696  * Returns     :  N/A
697  *
698  *********************************************************************/
699 void init_current_action (struct current_action_spec *dest)
700 {
701    memset(dest, '\0', sizeof(*dest));
702    dest->flags = ACTION_MOST_COMPATIBLE;
703 }
704
705
706 /*********************************************************************
707  *
708  * Function    :  merge_current_action
709  *
710  * Description :  Merge two actions together.
711  *                Similar to "dest += src".
712  *                Differences between this and merge_actions()
713  *                is that this one doesn't allocate memory for
714  *                strings (so "src" better be in memory for at least
715  *                as long as "dest" is, and you'd better free
716  *                "dest" using "current_free_action").
717  *                Also, there is no  mask or remove lists in dest.
718  *                (If we're applying it to a URL, we don't need them)
719  *
720  * Parameters  :
721  *          1  :  dest = Current actions, to modify.
722  *          2  :  src = Action to add.
723  *
724  * Returns     :  N/A
725  *
726  *********************************************************************/
727 void merge_current_action (struct current_action_spec *dest, 
728                            const struct action_spec *src)
729 {
730    int i;
731
732    dest->flags  &= src->mask;
733    dest->flags  |= src->add;
734
735    for (i = 0; i < ACTION_STRING_COUNT; i++)
736    {
737       char * str = src->string[i];
738       if (str)
739       {
740          dest->string[i] = str;
741       }
742    }
743
744    for (i = 0; i < ACTION_MULTI_COUNT; i++)
745    {
746       if (src->multi_remove_all[i])
747       {
748          /* Remove everything from dest */
749          destroy_list(dest->multi[i]);
750          list_duplicate(dest->multi[i], src->multi_add[i]);
751       }
752       else
753       {
754          list_remove_list(dest->multi[i], src->multi_remove[i]);
755          list_append_list_unique(dest->multi[i], src->multi_add[i]);
756       }
757    }
758 }
759
760
761 /*********************************************************************
762  *
763  * Function    :  free_current_action
764  *
765  * Description :  Free a current_action_spec.
766  *
767  * Parameters  :
768  *          1  :  src = Source to free.
769  *
770  * Returns     :  N/A
771  *
772  *********************************************************************/
773 void free_current_action (struct current_action_spec *src)
774 {
775    int i;
776
777    for (i = 0; i < ACTION_MULTI_COUNT; i++)
778    {
779       destroy_list(src->multi[i]);
780    }
781
782    memset(src, '\0', sizeof(*src));
783 }
784
785
786 /*********************************************************************
787  *
788  * Function    :  unload_actions_file
789  *
790  * Description :  Unloads an actions module.
791  *
792  * Parameters  :
793  *          1  :  file_data = the data structure associated with the
794  *                            actions file.
795  *
796  * Returns     :  N/A
797  *
798  *********************************************************************/
799 void unload_actions_file(void *file_data)
800 {
801    struct url_actions * next;
802    struct url_actions * cur = (struct url_actions *)file_data;
803    while (cur != NULL)
804    {
805       next = cur->next;
806       free_url(cur->url);
807       freez(cur);
808       cur = next;
809    }
810
811 }
812
813
814 /*********************************************************************
815  *
816  * Function    :  load_actions_file
817  *
818  * Description :  Read and parse a action file and add to files
819  *                list.
820  *
821  * Parameters  :
822  *          1  :  csp = Current client state (buffers, headers, etc...)
823  *
824  * Returns     :  0 => Ok, everything else is an error.
825  *
826  *********************************************************************/
827 int load_actions_file(struct client_state *csp)
828 {
829    static struct file_list *current_actions_file  = NULL;
830
831    FILE *fp;
832
833    struct url_actions *last_perm;
834    struct url_actions *perm;
835    char  buf[BUFSIZ];
836    struct file_list *fs;
837 #define MODE_START_OF_FILE 1
838 #define MODE_ACTIONS       2
839 #define MODE_ALIAS         3
840    int mode = MODE_START_OF_FILE;
841    struct action_spec cur_action[1];
842    struct action_alias * alias_list = NULL;
843
844    memset(cur_action, '\0', sizeof(*cur_action));
845
846    if (!check_file_changed(current_actions_file, csp->config->actions_file, &fs))
847    {
848       /* No need to load */
849       if (csp)
850       {
851          csp->actions_list = current_actions_file;
852       }
853       return 0;
854    }
855    if (!fs)
856    {
857       log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': error finding file: %E",
858                 csp->config->actions_file);
859       return 1; /* never get here */
860    }
861
862    fs->f = last_perm = (struct url_actions *)zalloc(sizeof(*last_perm));
863    if (last_perm == NULL)
864    {
865       log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': out of memory!",
866                 csp->config->actions_file);
867       return 1; /* never get here */
868    }
869
870    if ((fp = fopen(csp->config->actions_file, "r")) == NULL)
871    {
872       log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': error opening file: %E",
873                 csp->config->actions_file);
874       return 1; /* never get here */
875    }
876
877    while (read_config_line(buf, sizeof(buf), fp, fs) != NULL)
878    {
879       if (*buf == '{')
880       {
881          /* It's a header block */
882          if (buf[1] == '{')
883          {
884             /* It's {{settings}} or {{alias}} */
885             int len = strlen(buf);
886             char * start = buf + 2;
887             char * end = buf + len - 1;
888             if ((len < 5) || (*end-- != '}') || (*end-- != '}'))
889             {
890                /* too short */
891                fclose(fp);
892                log_error(LOG_LEVEL_FATAL, 
893                   "can't load actions file '%s': invalid line: %s",
894                   csp->config->actions_file, buf);
895                return 1; /* never get here */
896             }
897
898             /* Trim leading and trailing whitespace. */
899             end[1] = '\0';
900             chomp(start);
901
902             if (*start == '\0')
903             {
904                /* too short */
905                fclose(fp);
906                log_error(LOG_LEVEL_FATAL, 
907                   "can't load actions file '%s': invalid line: {{ }}",
908                   csp->config->actions_file);
909                return 1; /* never get here */
910             }
911
912             if (0 == strcmpic(start, "alias"))
913             {
914                /* it's an {{alias}} block */
915
916                mode = MODE_ALIAS;
917             }
918             else
919             {
920                /* invalid {{something}} block */
921                fclose(fp);
922                log_error(LOG_LEVEL_FATAL, 
923                   "can't load actions file '%s': invalid line: {{%s}}",
924                   csp->config->actions_file, start);
925                return 1; /* never get here */
926             }
927          }
928          else
929          {
930             /* It's an actions block */
931
932             char  actions_buf[BUFSIZ];
933             char * end;
934
935             /* set mode */
936             mode    = MODE_ACTIONS;
937
938             /* free old action */
939             free_action(cur_action);
940
941             /* trim { */
942             strcpy(actions_buf, buf + 1);
943
944             /* check we have a trailing } and then trim it */
945             end = actions_buf + strlen(actions_buf) - 1;
946             if (*end != '}')
947             {
948                /* too short */
949                fclose(fp);
950                log_error(LOG_LEVEL_FATAL, 
951                   "can't load actions file '%s': invalid line: %s",
952                   csp->config->actions_file, buf);
953                return 1; /* never get here */
954             }
955             *end = '\0';
956
957             /* trim any whitespace immediately inside {} */
958             chomp(actions_buf);
959
960             if (*actions_buf == '\0')
961             {
962                /* too short */
963                fclose(fp);
964                log_error(LOG_LEVEL_FATAL, 
965                   "can't load actions file '%s': invalid line: %s",
966                   csp->config->actions_file, buf);
967                return 1; /* never get here */
968             }
969
970             if (get_actions(actions_buf, alias_list, cur_action))
971             {
972                /* error */
973                fclose(fp);
974                log_error(LOG_LEVEL_FATAL, 
975                   "can't load actions file '%s': invalid line: %s",
976                   csp->config->actions_file, buf);
977                return 1; /* never get here */
978             }
979          }
980       }
981       else if (mode == MODE_ALIAS)
982       {
983          /* define an alias */
984          char  actions_buf[BUFSIZ];
985          struct action_alias * new_alias;
986          int more = 1;
987
988          char * start = strchr(buf, '=');
989          char * end = start;
990
991          if ((start == NULL) || (start == buf))
992          {
993             log_error(LOG_LEVEL_FATAL, 
994                "can't load actions file '%s': invalid alias line: %s",
995                csp->config->actions_file, buf);
996             return 1; /* never get here */
997          }
998
999          if ((new_alias = zalloc(sizeof(*new_alias))) == NULL)
1000          {
1001             fclose(fp);
1002             log_error(LOG_LEVEL_FATAL,
1003                "can't load actions file '%s': out of memory!",
1004                csp->config->actions_file);
1005             return 1; /* never get here */
1006          }
1007
1008          /* Eat any the whitespace before the '=' */
1009          end--;
1010          while ((*end == ' ') || (*end == '\t'))
1011          {
1012             /*
1013              * we already know we must have at least 1 non-ws char
1014              * at start of buf - no need to check
1015              */
1016             end--;
1017          }
1018          end[1] = '\0';
1019
1020          /* Eat any the whitespace after the '=' */
1021          start++;
1022          while ((*start == ' ') || (*start == '\t'))
1023          {
1024             start++;
1025          }
1026          if (*start == '\0')
1027          {
1028             log_error(LOG_LEVEL_FATAL, 
1029                "can't load actions file '%s': invalid alias line: %s",
1030                csp->config->actions_file, buf);
1031             return 1; /* never get here */
1032          }
1033
1034          new_alias->name = strdup(buf);
1035
1036          strcpy(actions_buf, start);
1037
1038          if (get_actions(actions_buf, alias_list, new_alias->action))
1039          {
1040             /* error */
1041             fclose(fp);
1042             log_error(LOG_LEVEL_FATAL, 
1043                "can't load actions file '%s': invalid alias line: %s = %s",
1044                csp->config->actions_file, buf, start);
1045             return 1; /* never get here */
1046          }
1047          
1048          /* add to list */
1049          new_alias->next = alias_list;
1050          alias_list = new_alias;
1051       }
1052       else if (mode == MODE_ACTIONS)
1053       {
1054          /* it's a URL pattern */
1055
1056          /* allocate a new node */
1057          if ((perm = zalloc(sizeof(*perm))) == NULL)
1058          {
1059             fclose(fp);
1060             log_error(LOG_LEVEL_FATAL,
1061                "can't load actions file '%s': out of memory!",
1062                csp->config->actions_file);
1063             return 1; /* never get here */
1064          }
1065
1066          /* Save flags */
1067          copy_action (perm->action, cur_action);
1068
1069          /* Save the URL pattern */
1070          if (create_url_spec(perm->url, buf))
1071          {
1072             fclose(fp);
1073             log_error(LOG_LEVEL_FATAL, 
1074                "can't load actions file '%s': cannot create URL pattern from: %s",
1075                csp->config->actions_file, buf);
1076             return 1; /* never get here */
1077          }
1078
1079          /* add it to the list */
1080          last_perm->next = perm;
1081          last_perm = perm;
1082       }
1083       else if (mode == MODE_START_OF_FILE)
1084       {
1085          /* oops - please have a {} line as 1st line in file. */
1086          fclose(fp);
1087          log_error(LOG_LEVEL_FATAL, 
1088             "can't load actions file '%s': first line is invalid: %s",
1089             csp->config->actions_file, buf);
1090          return 1; /* never get here */
1091       }
1092       else
1093       {
1094          /* How did we get here? This is impossible! */
1095          fclose(fp);
1096          log_error(LOG_LEVEL_FATAL, 
1097             "can't load actions file '%s': INTERNAL ERROR - mode = %d",
1098             csp->config->actions_file, mode);
1099          return 1; /* never get here */
1100       }
1101    }
1102
1103    fclose(fp);
1104    
1105    free_action(cur_action);
1106
1107    while (alias_list != NULL)
1108    {
1109       struct action_alias * next = alias_list->next;
1110       freez((char *)alias_list->name);
1111       free_action(alias_list->action);
1112       free(alias_list);
1113       alias_list = next;
1114    }
1115
1116 #ifndef SPLIT_PROXY_ARGS
1117    if (!suppress_blocklists)
1118    {
1119       fs->proxy_args = strsav(fs->proxy_args, "</pre>");
1120    }
1121 #endif /* ndef SPLIT_PROXY_ARGS */
1122
1123    /* the old one is now obsolete */
1124    if (current_actions_file)
1125    {
1126       current_actions_file->unloader = unload_actions_file;
1127    }
1128
1129    fs->next    = files->next;
1130    files->next = fs;
1131    current_actions_file = fs;
1132
1133    if (csp)
1134    {
1135       csp->actions_list = fs;
1136    }
1137
1138    return(0);
1139
1140 }