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