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