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