Conditional administrator e-mail address... some templates need colspan=2
[privoxy.git] / cgiedit.c
1 const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.2 2001/09/16 17:05:14 jongfoster Exp $";
2 /*********************************************************************
3  *
4  * File        :  $Source: /cvsroot/ijbswa/current/cgiedit.c,v $
5  *
6  * Purpose     :  CGI-based actionsfile editor.
7  *                
8  *                Functions declared include:
9  * 
10  *
11  * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
12  *                IJBSWA team.  http://ijbswa.sourceforge.net
13  *
14  *                Based on the Internet Junkbuster originally written
15  *                by and Copyright (C) 1997 Anonymous Coders and 
16  *                Junkbusters Corporation.  http://www.junkbusters.com
17  *
18  *                This program is free software; you can redistribute it 
19  *                and/or modify it under the terms of the GNU General
20  *                Public License as published by the Free Software
21  *                Foundation; either version 2 of the License, or (at
22  *                your option) any later version.
23  *
24  *                This program is distributed in the hope that it will
25  *                be useful, but WITHOUT ANY WARRANTY; without even the
26  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
27  *                PARTICULAR PURPOSE.  See the GNU General Public
28  *                License for more details.
29  *
30  *                The GNU General Public License should be included with
31  *                this file.  If not, you can view it at
32  *                http://www.gnu.org/copyleft/gpl.html
33  *                or write to the Free Software Foundation, Inc., 59
34  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
35  *
36  * Revisions   :
37  *    $Log: cgiedit.c,v $
38  *    Revision 1.2  2001/09/16 17:05:14  jongfoster
39  *    Removing unused #include showarg.h
40  *
41  *    Revision 1.1  2001/09/16 15:47:37  jongfoster
42  *    First version of CGI-based edit interface.  This is very much a
43  *    work-in-progress, and you can't actually use it to edit anything
44  *    yet.  You must #define FEATURE_CGI_EDIT_ACTIONS for these changes
45  *    to have any effect.
46  *
47  *
48  **********************************************************************/
49 \f
50
51 #include "config.h"
52
53 /*
54  * FIXME: Following includes copied from cgi.c - which are actually needed?
55  */
56
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <sys/types.h>
60 #include <stdlib.h>
61 #include <ctype.h>
62 #include <string.h>
63 #include <assert.h>
64 #include <limits.h>
65
66 #ifdef _WIN32
67 #define snprintf _snprintf
68 #endif /* def _WIN32 */
69
70 #include "project.h"
71 #include "cgi.h"
72 #include "cgiedit.h"
73 #include "cgisimple.h"
74 #include "list.h"
75 #include "encode.h"
76 #include "actions.h"
77 #include "miscutil.h"
78 #include "errlog.h"
79
80 const char cgiedit_h_rcs[] = CGIEDIT_H_VERSION;
81
82
83 #ifdef FEATURE_CGI_EDIT_ACTIONS
84
85 struct file_line
86 {
87    struct file_line * next;
88    char * raw;
89    char * prefix;
90    char * unprocessed;
91    int type;
92    
93    union
94    {
95       struct action_spec action[1];
96
97       struct
98       {
99          char * name;
100          char * svalue;
101          int ivalue;
102       } setting;
103
104       /* Add more data types here... e.g.
105
106
107       struct url_spec url[1];
108
109       struct
110       {
111          struct action_spec action[1];
112          const char * name;
113       } alias;
114
115       */
116
117    } data;
118 };
119
120 #define FILE_LINE_UNPROCESSED           1
121 #define FILE_LINE_BLANK                 2
122 #define FILE_LINE_ALIAS_HEADER          3
123 #define FILE_LINE_ALIAS_ENTRY           4
124 #define FILE_LINE_ACTION                5
125 #define FILE_LINE_URL                   6
126 #define FILE_LINE_SETTINGS_HEADER       7
127 #define FILE_LINE_SETTINGS_ENTRY        8
128 #define FILE_LINE_DESCRIPTION_HEADER    9
129 #define FILE_LINE_DESCRIPTION_ENTRY    10
130
131 /* FIXME: Following list of prototypes is not complete */
132 /* FIXME: Following non-static functions should be prototyped in .h or made static */
133 static int  simple_read_line(char **dest, FILE *fp);
134 static int  edit_read_line  (FILE *fp, char **raw_out, char **prefix_out, char **data_out);
135        int  edit_read_file  (FILE *fp, struct file_line ** pfile);
136        int  edit_write_file (const char * filename, const struct file_line * file);
137        void edit_free_file  (struct file_line * file);
138
139
140 /* FIXME: This should be in project.h and used everywhere */
141 #define JB_ERR_OK         0 /* Success, no error                        */
142 #define JB_ERR_MEMORY     1 /* Out of memory                            */
143 #define JB_ERR_CGI_PARAMS 2 /* Missing or corrupt CGI parameters        */
144 #define JB_ERR_FILE       3 /* Error opening, reading or writing a file */
145 #define JB_ERR_PARSE      4 /* Error parsing file                       */
146 #define JB_ERR_MODIFIED   5 /* File has been modified outside of the    */
147                             /* CGI actions editor.                      */
148
149
150 /*********************************************************************
151  *
152  * Function    :  simple_read_line
153  *
154  * Description :  Read a single line from a file and return it.
155  *                This is basically a version of fgets() that malloc()s
156  *                it's own line buffer.  Note that the buffer will
157  *                always be a multiple of BUFFER_SIZE bytes long.
158  *                Therefore if you are going to keep the string for
159  *                an extended period of time, you should probably
160  *                strdup() it and free() the original, to save memory.
161  *
162  *
163  * Parameters  :
164  *          1  :  dest = destination for newly malloc'd pointer to
165  *                line data.  Will be set to NULL on error.
166  *          2  :  fp = File to read from
167  *
168  * Returns     :  JB_ERR_OK     on success
169  *                JB_ERR_MEMORY on out-of-memory
170  *                JB_ERR_FILE   on EOF.
171  *
172  *********************************************************************/
173 static int simple_read_line(char **dest, FILE *fp)
174 {
175    int len;
176    char * buf;
177    char * newbuf;
178
179    assert(fp);
180    assert(dest);
181
182    *dest = NULL;
183
184    if (NULL == (buf = malloc(BUFFER_SIZE)))
185    {
186       return JB_ERR_MEMORY;
187    }
188    
189    *buf = '\0';
190    len = 0;
191
192    while (FOREVER)
193    {
194       newbuf = buf + len;
195       if ((!fgets(newbuf, BUFFER_SIZE, fp)) || (*newbuf == '\0'))
196       {
197          /* (*newbuf == '\0') should never happen unless fgets fails */
198          if (*buf == '\0')
199          {
200             free(buf);
201             return JB_ERR_FILE;
202          }
203          else
204          {
205             *dest = buf;
206             return JB_ERR_OK;
207          }
208       }
209       len = strlen(buf);
210       if ((buf[len - 1] == '\n') || (buf[len - 1] == '\r'))
211       {
212          *dest = buf;
213          return JB_ERR_OK;
214       }
215       
216       if (NULL == (newbuf = realloc(buf, len + BUFFER_SIZE)))
217       {
218          free(buf);
219          return JB_ERR_MEMORY;
220       }
221       buf = newbuf;
222    }
223 }
224
225
226 /*********************************************************************
227  *
228  * Function    :  edit_read_line
229  *
230  * Description :  Read a single non-empty line from a file and return
231  *                it.  Trims comments, leading and trailing whitespace
232  *                and respects escaping of newline and comment char.
233  *                Provides the line in 2 alternative forms: raw and
234  *                preprocessed.
235  *                - raw is the raw data read from the file.  If the 
236  *                  line is not modified, then this should be written
237  *                  to the new file.
238  *                - prefix is any comments and blank lines that were
239  *                  read from the file.  If the line is modified, then
240  *                  this should be written out to the file followed
241  *                  by the modified data.  (If this string is non-empty
242  *                  then it will have a newline at the end).
243  *                - data is the actual data that will be parsed
244  *                  further by appropriate routines.
245  *                On EOF, the 3 strings will all be set to NULL and
246  *                0 will be returned.
247  *
248  * Parameters  :
249  *          1  :  fp = File to read from
250  *          2  :  raw_out = destination for newly malloc'd pointer to
251  *                raw line data.  May be NULL if you don't want it.
252  *          3  :  prefix_out = destination for newly malloc'd pointer to
253  *                comments.  May be NULL if you don't want it.
254  *          4  :  data_out = destination for newly malloc'd pointer to
255  *                line data with comments and leading/trailing spaces
256  *                removed, and line continuation performed.  May be
257  *                NULL if you don't want it.
258  *
259  * Returns     :  JB_ERR_OK     on success
260  *                JB_ERR_MEMORY on out-of-memory
261  *                JB_ERR_FILE   on EOF.
262  *
263  *********************************************************************/
264 static int edit_read_line(FILE *fp, char **raw_out, char **prefix_out, char **data_out)
265 {
266    char *p;          /* Temporary pointer   */
267    char *linebuf;    /* Line read from file */
268    char *linestart;  /* Start of linebuf, usually first non-whitespace char */
269    char newline[3];  /* Used to store the newline - "\n", "\r", or "\r\n"   */
270    int contflag = 0; /* Nonzero for line continuation - i.e. line ends '\'  */
271    char *raw;        /* String to be stored in raw_out    */
272    char *prefix;     /* String to be stored in prefix_out */
273    char *data;       /* String to be stored in data_out   */
274    int rval = JB_ERR_OK;
275
276    assert(fp);
277
278    /* Set output parameters to NULL */
279    if (raw_out)
280    {
281       *raw_out    = NULL;
282    }
283    if (prefix_out)
284    {
285       *prefix_out = NULL;
286    }
287    if (data_out)
288    {
289       *data_out   = NULL;
290    }
291
292    /* Set string variables to new, empty strings. */
293
294    raw    = malloc(1);
295    prefix = malloc(1);
296    data   = malloc(1);
297
298    if ((raw == NULL) || (prefix == NULL) || (data == NULL))
299    {
300       freez(raw);
301       freez(prefix);
302       freez(data);
303       return JB_ERR_MEMORY;
304    }
305
306    *raw    = '\0';
307    *prefix = '\0';
308    *data   = '\0';
309
310    /* Main loop.  Loop while we need more data & it's not EOF. */
311
312    while ( (contflag || (*data == '\0'))
313         && (JB_ERR_OK == (rval = simple_read_line(&linebuf, fp))))
314    {
315       if (string_append(&raw,linebuf))
316       {
317          free(prefix);
318          free(data);
319          free(linebuf);
320          return JB_ERR_MEMORY;
321       }
322       
323       /* Trim off newline */
324       p = linebuf + strlen(linebuf);
325       if ((p != linebuf) && ((p[-1] == '\r') || (p[-1] == '\n')))
326       {
327          p--;
328          if ((p != linebuf) && ((p[-1] == '\r') || (p[-1] == '\n')))
329          {
330             p--;
331          }
332       }
333       strcpy(newline, p);
334       *p = '\0';
335
336       /* Line continuation? Trim escape and set flag. */
337       contflag = ((p != linebuf) && (*--p == '\\'));
338       if (contflag)
339       {
340          *p = '\0';
341       }
342
343       /* Trim leading spaces if we're at the start of the line */
344       linestart = linebuf;
345       if (*data == '\0')
346       {
347          /* Trim leading spaces */
348          while (*linestart && isspace((int)(unsigned char)*linestart))
349          {
350             linestart++;
351          }
352       }
353
354       /* Handle comment characters. */
355       p = linestart;
356       while ((p = strchr(p, '#')) != NULL)
357       {
358          /* Found a comment char.. */
359          if ((p != linebuf) && (*(p-1) == '\\'))
360          {
361             /* ..and it's escaped, left-shift the line over the escape. */
362             char *q = p - 1;
363             while ((*q = *(q + 1)) != '\0')
364             {
365                q++;
366             }
367             /* Now scan from just after the "#". */
368          }
369          else
370          {
371             /* Real comment.  Save it... */
372             if (p == linestart)
373             {
374                /* Special case:  Line only contains a comment, so all the
375                 * previous whitespace is considered part of the comment.
376                 * Undo the whitespace skipping, if any.
377                 */
378                linestart = linebuf;
379                p = linestart;
380             }
381             string_append(&prefix,p);
382             if (string_append(&prefix,newline))
383             {
384                free(raw);
385                free(data);
386                free(linebuf);
387                return JB_ERR_MEMORY;
388             }
389             *newline = '\0';
390
391             /* ... and chop off the rest of the line */
392             *p = '\0';
393          }
394       } /* END while (there's a # character) */
395
396       /* Write to the buffer */
397       if (*linestart)
398       {
399          if (string_append(&data, linestart))
400          {
401             free(raw);
402             free(prefix);
403             free(linebuf);
404             return JB_ERR_MEMORY;
405          }
406       }
407
408       free(linebuf);
409    } /* END while(we need more data) */
410
411    /* Handle simple_read_line() errors - ignore EOF */
412    if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
413    {
414       free(raw);
415       free(prefix);
416       free(data);
417       return rval;
418    }
419
420
421    if (*raw)
422    {
423       /* Got at least some data */
424
425       /* Remove trailing whitespace */         
426       chomp(data);
427
428       if (raw_out)
429       {
430          *raw_out    = raw;
431       }
432       else
433       {
434          free(raw);
435       }
436       if (prefix_out)
437       {
438          *prefix_out = prefix;
439       }
440       else
441       {
442          free(prefix);
443       }
444       if (data_out)
445       {
446          *data_out   = data;
447       }
448       else
449       {
450          free(data);
451       }
452       return(0);
453    }
454    else
455    {
456       /* EOF and no data there. */
457
458       free(raw);
459       free(prefix);
460       free(data);
461
462       return JB_ERR_FILE;
463    }
464 }
465
466
467 /*********************************************************************
468  *
469  * Function    :  edit_read_file
470  *
471  * Description :  Read a complete file into memory.  
472  *                Handles whitespace, comments and line continuation.
473  *
474  * Parameters  :
475  *          1  :  fp = File to read from
476  *          2  :  pfile = Destination for a linked list of file_lines.
477  *                        Will be set to NULL on error.
478  *
479  * Returns     :  JB_ERR_OK     on success
480  *                JB_ERR_MEMORY on out-of-memory
481  *
482  *********************************************************************/
483 int edit_read_file(FILE *fp, struct file_line ** pfile)
484 {
485    struct file_line * first_line; /* Keep for return value or to free */
486    struct file_line * cur_line;   /* Current line */
487    struct file_line * prev_line;  /* Entry with prev_line->next = cur_line */
488    int rval;
489
490    assert(fp);
491    assert(pfile);
492
493    *pfile = NULL;
494
495    cur_line = first_line = zalloc(sizeof(struct file_line));
496    if (cur_line == NULL)
497    {
498       return JB_ERR_MEMORY;
499    }
500
501    cur_line->type = FILE_LINE_UNPROCESSED;
502
503    rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed);
504    if (rval)
505    {
506       /* Out of memory or empty file. */
507       /* Note that empty file is not an error we propogate up */
508       free(cur_line);
509       return ((rval == JB_ERR_FILE) ? JB_ERR_OK : rval);
510    }
511
512    do
513    {
514       prev_line = cur_line;
515       cur_line = prev_line->next = zalloc(sizeof(struct file_line));
516       if (cur_line == NULL)
517       {
518          /* Out of memory */
519          edit_free_file(first_line);
520          return JB_ERR_MEMORY;
521       }
522
523       cur_line->type = FILE_LINE_UNPROCESSED;
524
525       rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed);
526       if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
527       {
528          /* Out of memory */
529          edit_free_file(first_line);
530          return JB_ERR_MEMORY;
531       }
532
533    }
534    while (rval != JB_ERR_FILE);
535
536    /* EOF */
537
538    /* We allocated one too many - free it */
539    prev_line->next = NULL;
540    free(cur_line);
541
542    *pfile = first_line;
543    return JB_ERR_OK;
544 }
545
546
547 /*********************************************************************
548  *
549  * Function    :  edit_write_file
550  *
551  * Description :  Write a complete file to disk.
552  *
553  * Parameters  :
554  *          1  :  filename = File to write to.
555  *          2  :  file = Data structure to write.
556  *
557  * Returns     :  JB_ERR_OK     on success
558  *                JB_ERR_FILE   on error writing to file.
559  *
560  *********************************************************************/
561 int edit_write_file(const char * filename, const struct file_line * file)
562 {
563    FILE * fp;
564
565    assert(filename);
566
567    if (NULL == (fp = fopen(filename, "wt")))
568    {
569       return JB_ERR_FILE;
570    }
571
572    while (file != NULL)
573    {
574       if (file->raw)
575       {
576          if (fputs(file->raw, fp) < 0)
577          {
578             fclose(fp);
579             return JB_ERR_FILE;
580          }
581       }
582       else
583       {
584          if (file->prefix)
585          {
586             if (fputs(file->prefix, fp) < 0)
587             {
588                fclose(fp);
589                return JB_ERR_FILE;
590             }
591          }
592          if (file->unprocessed)
593          {
594             if (fputs(file->unprocessed, fp) < 0)
595             {
596                fclose(fp);
597                return JB_ERR_FILE;
598             }
599             if (fputs("\n", fp) < 0)
600             {
601                fclose(fp);
602                return JB_ERR_FILE;
603             }
604          }
605          else
606          {
607             /* FIXME: Write data from file->data->whatever */
608             assert(0);
609          }
610       }
611       file = file->next;
612    }
613
614    fclose(fp);
615    return JB_ERR_OK;
616 }
617
618
619 /*********************************************************************
620  *
621  * Function    :  edit_free_file
622  *
623  * Description :  Free a complete file in memory.  
624  *
625  * Parameters  :
626  *          1  :  file = Data structure to free.
627  *
628  * Returns     :  N/A
629  *
630  *********************************************************************/
631 void edit_free_file(struct file_line * file)
632 {
633    struct file_line * next;
634
635    while (file != NULL)
636    {
637       next = file->next;
638       file->next = NULL;
639       freez(file->raw);
640       freez(file->prefix);
641       freez(file->unprocessed);
642       switch(file->type)
643       {
644          case 0: /* special case if memory zeroed */
645          case FILE_LINE_UNPROCESSED:
646          case FILE_LINE_BLANK:
647          case FILE_LINE_ALIAS_HEADER:
648          case FILE_LINE_SETTINGS_HEADER:
649          case FILE_LINE_DESCRIPTION_HEADER:
650          case FILE_LINE_DESCRIPTION_ENTRY:
651          case FILE_LINE_ALIAS_ENTRY:
652          case FILE_LINE_URL:
653             /* No data is stored for these */
654             break;
655
656          case FILE_LINE_ACTION:
657             free_action(file->data.action);
658             break;
659
660          case FILE_LINE_SETTINGS_ENTRY:
661             freez(file->data.setting.name);
662             freez(file->data.setting.svalue);
663             break;
664          default:
665             /* Should never happen */
666             assert(0);
667             break;
668       }
669       file->type = 0; /* paranoia */
670       free(file);
671       file = next;
672    }
673 }
674
675
676 /*********************************************************************
677  *
678  * Function    :  match_actions_file_header_line
679  *
680  * Description :  Match an actions file {{header}} line 
681  *
682  * Parameters  :
683  *          1  :  line - String from file
684  *          2  :  name - Header to match against
685  *
686  * Returns     :  0 iff they match.
687  *
688  *********************************************************************/
689 static int match_actions_file_header_line(const char * line, const char * name)
690 {
691    int len;
692
693    assert(line);
694    assert(name);
695
696    /* Look for "{{" */
697    if ((line[0] != '{') || (line[1] != '{'))
698    {
699       return 1;
700    }
701    line += 2;
702
703    /* Look for optional whitespace */
704    while ( (*line == ' ') || (*line == '\t') )
705    {
706       line++;
707    }
708
709    /* Look for the specified name (case-insensitive) */
710    len = strlen(name);
711    if (0 != strncmpic(line, name, len))
712    {
713       return 1;
714    }
715    line += len;
716
717    /* Look for optional whitespace */
718    while ( (*line == ' ') || (*line == '\t') )
719    {
720       line++;
721    }
722
723    /* Look for "}}" and end of string*/
724    if ((line[0] != '}') || (line[1] != '}') || (line[2] != '\0'))
725    {
726       return 1;
727    }
728
729    /* It matched!! */
730    return 0;
731 }
732
733
734 /*********************************************************************
735  *
736  * Function    :  match_actions_file_header_line
737  *
738  * Description :  Match an actions file {{header}} line 
739  *
740  * Parameters  :
741  *          1  :  line - String from file.  Must not start with
742  *                       whitespace (else infinite loop!)
743  *          2  :  name - Destination for name
744  *          2  :  name - Destination for value
745  *
746  * Returns     :  JB_ERR_OK     on success
747  *                JB_ERR_MEMORY on out-of-memory
748  *                JB_ERR_PARSE  if there's no "=" sign, or if there's
749  *                              nothing before the "=" sign (but empty
750  *                              values *after* the "=" sign are legal).
751  *
752  *********************************************************************/
753 static int split_line_on_equals(const char * line, char ** pname, char ** pvalue)
754 {
755    const char * name_end;
756    const char * value_start;
757    int name_len;
758
759    assert(line);
760    assert(pname);
761    assert(pvalue);
762    assert(*line != ' ');
763    assert(*line != '\t');
764
765    *pname = NULL;
766    *pvalue = NULL;
767
768    value_start = strchr(line, '=');
769    if ((value_start == NULL) || (value_start == line))
770    {
771       return JB_ERR_PARSE;
772    }
773
774    name_end = value_start - 1;
775
776    /* Eat any whitespace before the '=' */
777    while ((*name_end == ' ') || (*name_end == '\t'))
778    {
779       /*
780        * we already know we must have at least 1 non-ws char
781        * at start of buf - no need to check
782        */
783       name_end--;
784    }
785
786    name_len = name_end - line + 1; /* Length excluding \0 */
787    if (NULL == (*pname = (char *) malloc(name_len + 1)))
788    {
789       return JB_ERR_MEMORY;
790    }
791    strncpy(*pname, line, name_len);
792    (*pname)[name_len] = '\0';
793
794    /* Eat any the whitespace after the '=' */
795    value_start++;
796    while ((*value_start == ' ') || (*value_start == '\t'))
797    {
798       value_start++;
799    }
800
801    if (NULL == (*pvalue = strdup(value_start)))
802    {
803       free(*pname);
804       *pname = NULL;
805       return JB_ERR_MEMORY;
806    }
807
808    return JB_ERR_OK;
809 }
810
811
812 /*********************************************************************
813  *
814  * Function    :  edit_parse_actions_file
815  *
816  * Description :  Parse an actions file in memory.  
817  *
818  *                Passed linked list must have the "data" member
819  *                zeroed, and must contain valid "next" and
820  *                "unprocessed" fields.  The "raw" and "prefix"
821  *                fields are ignored, and "type" is just overwritten.
822  *
823  *                Note that on error the file may have been
824  *                partially parsed.
825  *
826  * Parameters  :
827  *          1  :  file = Actions file to be parsed in-place.
828  *
829  * Returns     :  JB_ERR_OK     on success
830  *                JB_ERR_MEMORY on out-of-memory
831  *                JB_ERR_PARSE  on error
832  *
833  *********************************************************************/
834 int edit_parse_actions_file(struct file_line * file)
835 {
836    struct file_line * cur_line;
837    int len;
838    const char * text; /* Text from a line */
839    char * name;  /* For lines of the form name=value */
840    char * value; /* For lines of the form name=value */
841    struct action_alias * alias_list = NULL;
842    int rval = JB_ERR_OK;
843
844    /* alias_list contains the aliases defined in this file.
845     * It might be better to use the "file_line.data" fields
846     * in the relavent places instead.
847     */
848
849    cur_line = file;
850
851    /* A note about blank line support: Blank lines should only 
852     * ever occur as the last line in the file.  This function
853     * is more forgiving than that - FILE_LINE_BLANK can occur
854     * anywhere.
855     */
856
857    /* Skip leading blanks.  Should only happen if file is
858     * empty (which is valid, but pointless).
859     */
860    while ( (cur_line != NULL)
861         && (cur_line->unprocessed[0] == '\0') )
862    {
863       /* Blank line */
864       cur_line->type = FILE_LINE_BLANK;
865       cur_line = cur_line->next;
866    }
867
868    if ( (cur_line != NULL)
869      && (cur_line->unprocessed[0] != '{') )
870    {
871       /* File doesn't start with a header */
872       return JB_ERR_PARSE;
873    }
874
875    if ( (cur_line != NULL) && (0 ==
876       match_actions_file_header_line(cur_line->unprocessed, "settings") ) )
877    {
878       cur_line->type = FILE_LINE_SETTINGS_HEADER;
879
880       cur_line = cur_line->next;
881       while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
882       {
883          if (cur_line->unprocessed[0])
884          {
885             cur_line->type = FILE_LINE_SETTINGS_ENTRY;
886
887             rval = split_line_on_equals(cur_line->unprocessed,
888                      &cur_line->data.setting.name,
889                      &cur_line->data.setting.svalue);
890             if (rval != JB_ERR_OK)
891             {
892                /* Line does not contain a name=value pair, or out-of-memory */
893                return rval;
894             }
895          }
896          else
897          {
898             cur_line->type = FILE_LINE_BLANK;
899          }
900          cur_line = cur_line->next;
901       }
902    }
903
904    if ( (cur_line != NULL) && (0 ==
905       match_actions_file_header_line(cur_line->unprocessed, "description") ) )
906    {
907       cur_line->type = FILE_LINE_DESCRIPTION_HEADER;
908
909       cur_line = cur_line->next;
910       while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
911       {
912          if (cur_line->unprocessed[0])
913          {
914             cur_line->type = FILE_LINE_DESCRIPTION_ENTRY;
915          }
916          else
917          {
918             cur_line->type = FILE_LINE_BLANK;
919          }
920          cur_line = cur_line->next;
921       }
922    }
923
924    if ( (cur_line != NULL) && (0 ==
925       match_actions_file_header_line(cur_line->unprocessed, "alias") ) )
926    {
927       cur_line->type = FILE_LINE_ALIAS_HEADER;
928
929       cur_line = cur_line->next;
930       while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
931       {
932          if (cur_line->unprocessed[0])
933          {
934             /* define an alias */
935             struct action_alias * new_alias;
936
937             cur_line->type = FILE_LINE_ALIAS_ENTRY;
938
939             rval = split_line_on_equals(cur_line->unprocessed, &name, &value);
940             if (rval != JB_ERR_OK)
941             {
942                /* Line does not contain a name=value pair, or out-of-memory */
943                return rval;
944             }
945
946             if ((new_alias = zalloc(sizeof(*new_alias))) == NULL)
947             {
948                /* Out of memory */
949                free(name);
950                free(value);
951                free_alias_list(alias_list);
952                return JB_ERR_MEMORY;
953             }
954
955             if (get_actions(value, alias_list, new_alias->action))
956             {
957                /* Invalid action or out of memory */
958                free(name);
959                free(value);
960                free(new_alias);
961                free_alias_list(alias_list);
962                return JB_ERR_PARSE; /* FIXME: or JB_ERR_MEMORY */
963             }
964
965             free(value);
966
967             new_alias->name = name;
968
969             /* add to list */
970             new_alias->next = alias_list;
971             alias_list = new_alias;
972          }
973          else
974          {
975             cur_line->type = FILE_LINE_BLANK;
976          }
977          cur_line = cur_line->next;
978       }
979    }
980
981    /* Header done, process the main part of the file */
982    while (cur_line != NULL)
983    {
984       /* At this point, (cur_line->unprocessed[0] == '{') */
985       assert(cur_line->unprocessed[0] == '{');
986       text = cur_line->unprocessed + 1;
987       len = strlen(text) - 1;
988       if (text[len] != '}')
989       {
990          /* No closing } on header */
991          free_alias_list(alias_list);
992          return JB_ERR_PARSE;
993       }
994
995       if (text[0] == '{')
996       {
997          /* An invalid {{ header.  */
998          free_alias_list(alias_list);
999          return JB_ERR_PARSE;
1000       }
1001
1002       while ( (*text == ' ') || (*text == '\t') )
1003       {
1004          text++;
1005          len--;
1006       }
1007       while ( (len > 0)
1008            && ( (text[len - 1] == ' ')
1009              || (text[len - 1] == '\t') ) )
1010       {
1011          len--;
1012       }
1013       if (len <= 0)
1014       {
1015          /* A line containing just { } */
1016          free_alias_list(alias_list);
1017          return JB_ERR_PARSE;
1018       }
1019
1020       cur_line->type = FILE_LINE_ACTION;
1021
1022       /* Remove {} and make copy */
1023       if (NULL == (value = (char *) malloc(len + 1)))
1024       {
1025          /* Out of memory */
1026          free_alias_list(alias_list);
1027          return JB_ERR_MEMORY;
1028       }
1029       strncpy(value, text, len);
1030       value[len] = '\0';
1031
1032       /* Get actions */
1033       if (get_actions(value, alias_list, cur_line->data.action))
1034       {
1035          /* Invalid action or out of memory */
1036          free(value);
1037          free_alias_list(alias_list);
1038          return JB_ERR_PARSE; /* FIXME: or JB_ERR_MEMORY */
1039       }
1040
1041       /* Done with string - it was clobbered anyway */
1042       free(value);
1043
1044       /* Process next line */
1045       cur_line = cur_line->next;
1046
1047       /* Loop processing URL patterns */
1048       while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1049       {
1050          if (cur_line->unprocessed[0])
1051          {
1052             /* Could parse URL here, but this isn't currently needed */
1053
1054             cur_line->type = FILE_LINE_URL;
1055          }
1056          else
1057          {
1058             cur_line->type = FILE_LINE_BLANK;
1059          }
1060          cur_line = cur_line->next;
1061       }
1062    } /* End main while(cur_line != NULL) loop */
1063
1064    free_alias_list(alias_list);
1065
1066    return JB_ERR_OK;
1067 }
1068
1069
1070 /*********************************************************************
1071  *
1072  * Function    :  edit_read_file
1073  *
1074  * Description :  Read a complete actions file into memory and
1075  *                parses it.
1076  *
1077  * Parameters  :
1078  *          1  :  filename = Path to file to read from
1079  *          2  :  pfile = Destination for a linked list of file_lines.
1080  *                        Will be set to NULL on error.
1081  *
1082  * Returns     :  JB_ERR_OK     on success
1083  *                JB_ERR_MEMORY on out-of-memory
1084  *                JB_ERR_FILE   if the file cannot be opened or
1085  *                              contains no data
1086  *
1087  *********************************************************************/
1088 int edit_read_actions_file(const char * filename, struct file_line ** pfile)
1089 {
1090    struct file_line * file;
1091    FILE * fp;
1092    int rval;
1093
1094    assert(filename);
1095    assert(pfile);
1096
1097    *pfile = NULL;
1098
1099    if (NULL == (fp = fopen(filename,"rt")))
1100    {
1101       return JB_ERR_FILE;
1102    }
1103
1104    rval = edit_read_file(fp, &file);
1105
1106    fclose(fp);
1107
1108    if (JB_ERR_OK != rval)
1109    {
1110       return rval;
1111    }
1112
1113    if (JB_ERR_OK != (rval = edit_parse_actions_file(file)))
1114    {
1115       edit_free_file(file);
1116       return rval;
1117    }
1118
1119    *pfile = file;
1120    return JB_ERR_OK;
1121 }
1122
1123
1124 /*********************************************************************
1125  *
1126  * Function    :  get_file_name_param
1127  *
1128  * Description :  Get the name of the file to edit from the parameters
1129  *                passed to a CGI function.  This function handles
1130  *                security checks such as blocking urls containing
1131  *                "/" or ".", prepending the config file directory,
1132  *                and adding the specified suffix.
1133  *
1134  *                (This is an essential security check, otherwise
1135  *                users may be able to pass "../../../etc/passwd"
1136  *                and overwrite the password file [linux], "prn:"
1137  *                and print random data [Windows], etc...)
1138  *
1139  *                This function only allows filenames contining the
1140  *                characters '-', '_', 'A'-'Z', 'a'-'z', and '0'-'9'.
1141  *                That's probably too restrictive but at least it's
1142  *                secure.
1143  *
1144  * Parameters  :
1145  *           1 :  csp = Current client state (buffers, headers, etc...)
1146  *           2 :  parameters = map of cgi parameters
1147  *           3 :  suffix = File extension, e.g. ".actions"
1148  *           4 :  pfilename = destination for full filename.  Caller
1149  *                free()s.  Set to NULL on error.
1150  *           5 :  pparam = destination for partial filename,
1151  *                suitable for use in another URL.  Allocated as part
1152  *                of the map "parameters", so don't free it.
1153  *                Set to NULL if not specified.
1154  *
1155  * CGI Parameters : None
1156  *
1157  * Returns     :  JB_ERR_OK         on success
1158  *                JB_ERR_MEMORY     on out-of-memory
1159  *                JB_ERR_CGI_PARAMS if "filename" was not specified
1160  *                                  or is not valid.
1161  *
1162  *********************************************************************/
1163 static int get_file_name_param(struct client_state *csp,
1164                                       struct map *parameters,
1165                                char *suffix,
1166                                char **pfilename,
1167                                const char **pparam)
1168 {
1169    const char *param;
1170    const char *s;
1171    char *name;
1172    char *fullpath;
1173    char ch;
1174    int len;
1175
1176    assert(csp);
1177    assert(parameters);
1178    assert(suffix);
1179    assert(pfilename);
1180    assert(pparam);
1181
1182    *pfilename = NULL;
1183    *pparam = NULL;
1184
1185    param = lookup(parameters, "filename");
1186    if (!*param)
1187    {
1188       return JB_ERR_CGI_PARAMS;
1189    }
1190
1191    *pparam = param;
1192
1193    len = strlen(param);
1194    if (len >= FILENAME_MAX)
1195    {
1196       /* Too long. */
1197       return JB_ERR_CGI_PARAMS;
1198    }
1199
1200    /* Check every character to see if it's legal */
1201    s = param;
1202    while ((ch = *s++) != '\0')
1203    {
1204       if ( ((ch < 'A') || (ch > 'Z'))
1205         && ((ch < 'a') || (ch > 'z'))
1206         && ((ch < '0') || (ch > '9'))
1207         && (ch != '-')
1208         && (ch != '_') )
1209       {
1210          /* Probable hack attempt. */
1211          return JB_ERR_CGI_PARAMS;
1212       }
1213    }
1214
1215    /* Append extension */
1216    name = malloc(len + strlen(suffix) + 1);
1217    if (name == NULL)
1218    {
1219       return JB_ERR_MEMORY;
1220    }
1221    strcpy(name, param);
1222    strcpy(name + len, suffix);
1223
1224    /* Prepend path */
1225    fullpath = make_path(csp->config->confdir, name);
1226    free(name);
1227    if (fullpath == NULL)
1228    {
1229       return JB_ERR_MEMORY;
1230    }
1231
1232    /* Success */
1233    *pfilename = fullpath;
1234
1235    return JB_ERR_OK;
1236 }
1237
1238
1239 /*********************************************************************
1240  *
1241  * Function    :  get_number_param
1242  *
1243  * Description :  Get a non-negative integer from the parameters
1244  *                passed to a CGI function.
1245  *
1246  * Parameters  :
1247  *           1 :  csp = Current client state (buffers, headers, etc...)
1248  *           2 :  parameters = map of cgi parameters
1249  *           3 :  name = Name of CGI parameter to read
1250  *           4 :  pvalue = destination for value.
1251  *                         Set to -1 on error.
1252  *
1253  * CGI Parameters : None
1254  *
1255  * Returns     :  JB_ERR_OK         on success
1256  *                JB_ERR_MEMORY     on out-of-memory
1257  *                JB_ERR_CGI_PARAMS if "filename" was not specified
1258  *                                  or is not valid.
1259  *
1260  *********************************************************************/
1261 static int get_number_param(struct client_state *csp,
1262                                    struct map *parameters,
1263                             char *name,
1264                             int *pvalue)
1265 {
1266    const char *param;
1267    char ch;
1268    int value;
1269
1270    assert(csp);
1271    assert(parameters);
1272    assert(name);
1273    assert(pvalue);
1274
1275    *pvalue = -1;
1276
1277    param = lookup(parameters, name);
1278    if (!*param)
1279    {
1280       return JB_ERR_CGI_PARAMS;
1281    }
1282
1283    /* We don't use atoi because I want to check this carefully... */
1284
1285    value = 0;
1286    while ((ch = *param++) != '\0')
1287    {
1288       if ((ch < '0') || (ch > '9'))
1289       {
1290          return JB_ERR_CGI_PARAMS;
1291       }
1292
1293       ch -= '0';
1294
1295       /* Note:
1296        *
1297        * <limits.h> defines INT_MAX
1298        *
1299        * (INT_MAX - ch) / 10 is the largest number that 
1300        *     can be safely multiplied by 10 then have ch added.
1301        */
1302       if (value > ((INT_MAX - ch) / 10))
1303       {
1304          return JB_ERR_CGI_PARAMS;
1305       }
1306
1307       value = value * 10 + ch;
1308    }
1309
1310    /* Success */
1311    *pvalue = value;
1312
1313    return JB_ERR_OK;
1314 }
1315
1316
1317 /*********************************************************************
1318  *
1319  * Function    :  cgi_edit_actions
1320  *
1321  * Description :  CGI function that allows the user to choose which
1322  *                actions file to edit.
1323  *
1324  * Parameters  :
1325  *           1 :  csp = Current client state (buffers, headers, etc...)
1326  *           2 :  rsp = http_response data structure for output
1327  *           3 :  parameters = map of cgi parameters
1328  *
1329  * CGI Parameters : None
1330  *
1331  * Returns     :  0 on success, nonzero on error
1332  *
1333  *********************************************************************/
1334 int cgi_edit_actions(struct client_state *csp,
1335                      struct http_response *rsp,
1336                             struct map *parameters)
1337 {
1338
1339    /* FIXME: Incomplete */
1340    rsp->status = strdup("302 Local Redirect from Junkbuster");
1341    enlist_unique_header(rsp->headers, "Location", "http://ijbswa.sourceforge.net/config/edit-actions-list?filename=edit");
1342
1343    return 0;
1344 }
1345
1346
1347 /*********************************************************************
1348  *
1349  * Function    :  cgi_edit_actions_list
1350  *
1351  * Description :  CGI function that edits the actions list.
1352  *                FIXME: This function shouldn't FATAL ever.
1353  *                FIXME: This function doesn't check the retval of map()
1354  * Parameters  :
1355  *           1 :  csp = Current client state (buffers, headers, etc...)
1356  *           2 :  rsp = http_response data structure for output
1357  *           3 :  parameters = map of cgi parameters
1358  *
1359  * CGI Parameters : None
1360  *
1361  * Returns     :  0 on success, 1 on error.
1362  *
1363  *********************************************************************/
1364 int cgi_edit_actions_list(struct client_state *csp, struct http_response *rsp,
1365                                  struct map *parameters)
1366 {
1367    char * section_template;
1368    char * url_template;
1369    char * sections;
1370    char * urls;
1371    char buf[50];
1372    char * s;
1373    struct map * exports;
1374    struct map * section_exports;
1375    struct map * url_exports;
1376    struct file_line * file;
1377    struct file_line * cur_line;
1378    int line_number = 0;
1379    int url_1_2;
1380    int rval;
1381    char * filename;
1382    char * filename_param;
1383
1384    rval = get_file_name_param(csp, parameters, ".action", &filename, &filename_param);
1385    if (rval)
1386    {
1387       /* No filename specified. */
1388       /* FIXME: Shouldn't FATAL here */
1389       log_error(LOG_LEVEL_FATAL, "No filename specified");
1390       return 1;
1391    }
1392
1393    if (edit_read_actions_file(filename, &file))
1394    {
1395       /* FIXME: Shouldn't FATAL here */
1396       log_error(LOG_LEVEL_FATAL, "Cannot load file '%s' for editing", filename);
1397       return 1;
1398    }
1399
1400    free(filename);
1401
1402    if (NULL == (exports = default_exports(csp, NULL)))
1403    {
1404       log_error(LOG_LEVEL_FATAL, "Out of memory in cgi_edit_actions_list");
1405       return 1;
1406    }
1407
1408    map(exports, "filename", 1, filename_param, 1);
1409
1410    /* Should do all global exports above this point */
1411
1412    if (NULL == (section_template = template_load(csp, "edit-actions-list-section")))
1413    {
1414       log_error(LOG_LEVEL_FATAL, "Out of memory in cgi_edit_actions_list");
1415       return 1;
1416    }
1417    if (NULL == (url_template = template_load(csp, "edit-actions-list-url")))
1418    {
1419       log_error(LOG_LEVEL_FATAL, "Out of memory in cgi_edit_actions_list");
1420       return 1;
1421    }
1422
1423    template_fill(&section_template, exports);
1424    template_fill(&url_template, exports);
1425
1426    /* Find start of actions in file */
1427    cur_line = file;
1428    line_number = 1;
1429    while ((cur_line != NULL) && (cur_line->type != FILE_LINE_ACTION))
1430    {
1431       cur_line = cur_line->next;
1432       line_number++;
1433    }
1434
1435    if (NULL == (sections = strdup("")))
1436    {
1437       log_error(LOG_LEVEL_FATAL, "Out of memory in cgi_edit_actions_list");
1438       return 1;
1439    }
1440
1441    while ((cur_line != NULL) && (cur_line->type == FILE_LINE_ACTION))
1442    {
1443       if (NULL == (section_exports = new_map()))
1444       {
1445          log_error(LOG_LEVEL_FATAL, "Out of memory in cgi_edit_actions_list");
1446          return 1;
1447       }
1448
1449       snprintf(buf, 50, "%d", line_number);
1450       map(section_exports, "sectionid", 1, buf, 1);
1451
1452       if (NULL == (s = actions_to_html(cur_line->data.action)))
1453       {
1454          log_error(LOG_LEVEL_FATAL, "Out of memory in cgi_edit_actions_list");
1455          return 1;
1456       }
1457       map(section_exports, "actions", 1, s, 0);
1458
1459       /* Should do all section-specific exports above this point */
1460
1461       if (NULL == (urls = strdup("")))
1462       {
1463          log_error(LOG_LEVEL_FATAL, "Out of memory in cgi_edit_actions_list");
1464          return 1;
1465       }
1466
1467       url_1_2 = 2;
1468
1469       cur_line = cur_line->next;
1470       line_number++;
1471
1472       while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL))
1473       {
1474          if (NULL == (url_exports = new_map()))
1475          {
1476             log_error(LOG_LEVEL_FATAL, "Out of memory in cgi_edit_actions_list");
1477             return 1;
1478          }
1479
1480          snprintf(buf, 50, "%d", line_number);
1481          map(url_exports, "urlid", 1, buf, 1);
1482
1483          snprintf(buf, 50, "%d", url_1_2);
1484          map(url_exports, "url-1-2", 1, buf, 1);
1485
1486          if (NULL == (s = html_encode(cur_line->unprocessed)))
1487          {
1488             log_error(LOG_LEVEL_FATAL, "Out of memory in cgi_edit_actions_list");
1489             return 1;
1490          }
1491
1492          map(url_exports, "url", 1, s, 0);
1493
1494          if (NULL == (s = strdup(url_template)))
1495          {
1496             log_error(LOG_LEVEL_FATAL, "Out of memory in cgi_edit_actions_list");
1497             return 1;
1498          }
1499          template_fill(&s, section_exports);
1500          template_fill(&s, url_exports);
1501          urls = strsav(urls, s);
1502          free_map(url_exports);
1503
1504          url_1_2 = 3 - url_1_2;
1505
1506          cur_line = cur_line->next;
1507          line_number++;
1508       }
1509
1510       map(section_exports, "urls", 1, urls, 0);
1511
1512       /* Could also do section-specific exports here, but it wouldn't be as fast */
1513
1514       s = strdup(section_template);
1515       template_fill(&s, section_exports);
1516       sections = strsav(sections, s);
1517       free_map(section_exports);
1518    }
1519
1520    edit_free_file(file);
1521
1522    map(exports, "sections", 1, sections, 0);
1523
1524    /* Could also do global exports here, but it wouldn't be as fast */
1525
1526    rsp->body = template_load(csp, "edit-actions-list");
1527    template_fill(&rsp->body, exports);
1528    free_map(exports);
1529
1530    return 0;
1531 }
1532
1533
1534 /*********************************************************************
1535  *
1536  * Function    :  map_radio
1537  *
1538  * Description :  Map a set of radio button values.  E.g. if you have
1539  *                3 radio buttons, declare them as:
1540  *                  <option type="radio" name="xyz" @xyz-a@>
1541  *                  <option type="radio" name="xyz" @xyz-b@>
1542  *                  <option type="radio" name="xyz" @xyz-c@>
1543  *                Then map one of the @xyz-?@ variables to "checked"
1544  *                and all the others to empty by calling:
1545  *                map_radio(exports, "xyz", "abc", sel)
1546  *                Where 'sel' is 'a', 'b', or 'c'.
1547  *
1548  * Parameters  :
1549  *           1 :  exports = Exports map to modify.
1550  *           2 :  optionname = name for map
1551  *           3 :  values = null-terminated list of values;
1552  *           4 :  value = Selected value.
1553  *
1554  * CGI Parameters : None
1555  *
1556  * Returns     :  JB_ERR_OK     on success
1557  *                JB_ERR_MEMORY on out-of-memory
1558  *
1559  *********************************************************************/
1560 static int map_radio(struct map * exports,
1561                      const char * optionname, 
1562                      const char * values,
1563                      char value)
1564 {
1565    int len;
1566    char * buf;
1567    char * p;
1568    char c;
1569    
1570    assert(exports);
1571    assert(optionname);
1572    assert(values);
1573
1574    len = strlen(optionname);
1575    buf = malloc(len + 3);
1576    if (buf == NULL)
1577    {
1578       return JB_ERR_MEMORY;
1579    }
1580
1581    strcpy(buf, optionname);
1582    p = buf + len;
1583    *p++ = '-';
1584    p[1] = '\0';
1585
1586    while ((c = *values++) != '\0')
1587    {
1588       if (c != value)
1589       {
1590          *p = c;
1591          if (map(exports, buf, 1, "", 1))
1592          {
1593             free(buf);
1594             return JB_ERR_MEMORY;
1595          }
1596       }
1597    }
1598
1599    *p = value;
1600    if (map(exports, buf, 0, "checked", 1))
1601    {
1602       free(buf);
1603       return JB_ERR_MEMORY;
1604    }
1605
1606    return JB_ERR_OK;
1607 }
1608
1609
1610 /*********************************************************************
1611  *
1612  * Function    :  actions_to_radio
1613  *
1614  * Description :  Converts a actionsfile entry into settings for
1615  *                radio buttons and edit boxes on a HTML form.
1616  *
1617  * Parameters  :
1618  *          1  :  exports = List of substitutions to add to.
1619  *          2  :  action  = Action to read
1620  *
1621  * Returns     :  JB_ERR_OK     on success
1622  *                JB_ERR_MEMORY on out-of-memory
1623  *
1624  *********************************************************************/
1625 static int actions_to_radio(struct map * exports, const struct action_spec *action)
1626 {
1627    unsigned mask = action->mask;
1628    unsigned add  = action->add;
1629    int mapped_param;
1630    int checked;
1631    char current_mode;
1632
1633    assert(exports);
1634    assert(action);
1635
1636    mask = action->mask;
1637    add  = action->add;
1638
1639    /* sanity - prevents "-feature +feature" */
1640    mask |= add;
1641
1642
1643 #define DEFINE_ACTION_BOOL(name, bit)                 \
1644    if (!(mask & bit))                                 \
1645    {                                                  \
1646       current_mode = 'n';                             \
1647    }                                                  \
1648    else if (add & bit)                                \
1649    {                                                  \
1650       current_mode = 'y';                             \
1651    }                                                  \
1652    else                                               \
1653    {                                                  \
1654       current_mode = 'x';                             \
1655    }                                                  \
1656    if (map_radio(exports, name, "ynx", current_mode)) \
1657    {                                                  \
1658       return JB_ERR_MEMORY;                           \
1659    }
1660
1661 #define DEFINE_ACTION_STRING(name, bit, index)        \
1662    DEFINE_ACTION_BOOL(name, bit);                     \
1663    mapped_param = 0;
1664
1665 #define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default)  \
1666    if (add & bit)                                                    \
1667    {                                                                 \
1668       checked = !strcmp(action->string[index], value);               \
1669    }                                                                 \
1670    else                                                              \
1671    {                                                                 \
1672       checked = is_default;                                          \
1673    }                                                                 \
1674    mapped_param |= checked;                                          \
1675    if (map(exports, name "-param-" value, 1, (checked ? "checked" : ""), 1)) \
1676    {                                                                 \
1677       return JB_ERR_MEMORY;                                          \
1678    }
1679
1680 #define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val)       \
1681    if (map(exports, name "-param-custom", 1,                         \
1682            ((!mapped_param) ? "checked" : ""), 1))                   \
1683    {                                                                 \
1684       return JB_ERR_MEMORY;                                          \
1685    }                                                                 \
1686    if (map(exports, name "-param", 1,                                \
1687            (((add & bit) && !mapped_param) ?                         \
1688            action->string[index] : default_val), 1))                 \
1689    {                                                                 \
1690       return JB_ERR_MEMORY;                                          \
1691    }
1692
1693 #define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val)     \
1694    if (map(exports, name "-param", 1,                                \
1695            ((add & bit) ? action->string[index] : default_val), 1))  \
1696    {                                                                 \
1697       return JB_ERR_MEMORY;                                          \
1698    }
1699
1700 #define DEFINE_ACTION_MULTI(name, index)              \
1701    if (action->multi_add[index]->first)               \
1702    {                                                  \
1703       current_mode = 'y';                             \
1704    }                                                  \
1705    else if (action->multi_remove_all[index])          \
1706    {                                                  \
1707       current_mode = 'n';                             \
1708    }                                                  \
1709    else if (action->multi_remove[index]->first)       \
1710    {                                                  \
1711       current_mode = 'y';                             \
1712    }                                                  \
1713    else                                               \
1714    {                                                  \
1715       current_mode = 'x';                             \
1716    }                                                  \
1717    if (map_radio(exports, name, "ynx", current_mode)) \
1718    {                                                  \
1719       return JB_ERR_MEMORY;                           \
1720    }
1721
1722 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
1723
1724 #include "actionlist.h"
1725
1726 #undef DEFINE_ACTION_MULTI
1727 #undef DEFINE_ACTION_STRING
1728 #undef DEFINE_ACTION_BOOL
1729 #undef DEFINE_ACTION_ALIAS
1730 #undef DEFINE_CGI_PARAM_CUSTOM
1731 #undef DEFINE_CGI_PARAM_RADIO
1732 #undef DEFINE_CGI_PARAM_NO_RADIO
1733
1734    return JB_ERR_OK;
1735 }
1736
1737
1738 /*********************************************************************
1739  *
1740  * Function    :  cgi_edit_actions
1741  *
1742  * Description :  CGI function that edits the Actions list.
1743  *
1744  * Parameters  :
1745  *           1 :  csp = Current client state (buffers, headers, etc...)
1746  *           2 :  rsp = http_response data structure for output
1747  *           3 :  parameters = map of cgi parameters
1748  *
1749  * CGI Parameters : None
1750  *
1751  * Returns     :  0 on success, nonzero on error
1752  *
1753  *********************************************************************/
1754 int cgi_edit_actions_for_url(struct client_state *csp,
1755                              struct http_response *rsp,
1756                                     struct map *parameters)
1757 {
1758    struct map * exports;
1759    int sectionid;
1760
1761    struct file_line * file;
1762    struct file_line * cur_line;
1763    int line_number;
1764    int rval;
1765    char * filename;
1766    char * filename_param;
1767
1768    rval = get_file_name_param(csp, parameters, ".action", &filename, &filename_param);
1769    if (rval)
1770    {
1771       /* No filename specified. */
1772       /* FIXME: Shouldn't FATAL here */
1773       log_error(LOG_LEVEL_FATAL, "No filename specified");
1774       return 1;
1775    }
1776
1777    if (get_number_param(csp, parameters, "section", &sectionid))
1778    {
1779       /* FIXME: Shouldn't FATAL here */
1780       log_error(LOG_LEVEL_FATAL, "No 'section' parameter");
1781       return cgi_default(csp, rsp, parameters);
1782    }
1783
1784    if (edit_read_actions_file(filename, &file))
1785    {
1786       /* FIXME: Shouldn't FATAL here */
1787       log_error(LOG_LEVEL_FATAL, "Cannot load file '%s' for editing", filename);
1788       return 1;
1789    }
1790    cur_line = file;
1791
1792    for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
1793    {
1794       cur_line = cur_line->next;
1795    }
1796
1797    if ( (cur_line == NULL)
1798      || (line_number != sectionid)
1799      || (sectionid < 1)
1800      || (cur_line->type != FILE_LINE_ACTION))
1801    {
1802       /* FIXME: Shouldn't FATAL here */
1803       log_error(LOG_LEVEL_FATAL, "Bad sectionid!");
1804       return 1;
1805    }
1806
1807    exports = default_exports(csp, NULL);
1808    map(exports, "filename", 1, filename_param, 1);
1809    map(exports, "section", 1, lookup(parameters, "section"), 1);
1810
1811    actions_to_radio(exports, cur_line->data.action);
1812
1813    edit_free_file(file);
1814
1815    rsp->body = template_load(csp, "edit-actions-for-url");
1816    template_fill(&rsp->body, exports);
1817    free_map(exports);
1818
1819    return 0;
1820 }
1821
1822
1823 /*********************************************************************
1824  *
1825  * Function    :  actions_from_radio
1826  *
1827  * Description :  Converts a map of parameters passed to a CGI function
1828  *                into an actionsfile entry.
1829  *
1830  * Parameters  :
1831  *          1  :  parameters = parameters to the CGI call
1832  *          2  :  action  = Action to change.  Must be valid before
1833  *                          the call, actions not specified will be
1834  *                          left unchanged.
1835  *
1836  * Returns     :  JB_ERR_OK     on success
1837  *                JB_ERR_MEMORY on out-of-memory
1838  *
1839  *********************************************************************/
1840 static int actions_from_radio(const struct map * parameters,
1841                               struct action_spec *action)
1842 {
1843    const char * param;
1844    char * param_dup;
1845    char ch;
1846
1847    assert(parameters);
1848    assert(action);
1849
1850 #define DEFINE_ACTION_BOOL(name, bit)                 \
1851    if (NULL != (param = lookup(parameters, name)))    \
1852    {                                                  \
1853       ch = toupper((int)param[0]);                    \
1854       if (ch == 'Y')                                  \
1855       {                                               \
1856          action->add  |= bit;                         \
1857          action->mask |= bit;                         \
1858       }                                               \
1859       else if (ch == 'N')                             \
1860       {                                               \
1861          action->add  &= ~bit;                        \
1862          action->mask &= ~bit;                        \
1863       }                                               \
1864       else if (ch == 'X')                             \
1865       {                                               \
1866          action->add  &= ~bit;                        \
1867          action->mask |= bit;                         \
1868       }                                               \
1869    }
1870
1871 #define DEFINE_ACTION_STRING(name, bit, index)                    \
1872    if (NULL != (param = lookup(parameters, name)))                \
1873    {                                                              \
1874       ch = toupper((int)param[0]);                                \
1875       if (ch == 'Y')                                              \
1876       {                                                           \
1877          param = lookup(parameters, name "-mode");                \
1878          if ((*param == '\0') || (0 == strcmp(param, "CUSTOM")))  \
1879          {                                                        \
1880             param = lookup(parameters, name "-param");            \
1881          }                                                        \
1882          if (*param != '\0')                                      \
1883          {                                                        \
1884             if (NULL == (param_dup = strdup(param)))              \
1885             {                                                     \
1886                return JB_ERR_MEMORY;                              \
1887             }                                                     \
1888             freez(action->string[index]);                         \
1889             action->add  |= bit;                                  \
1890             action->mask |= bit;                                  \
1891             action->string[index] = param_dup;                    \
1892          }                                                        \
1893       }                                                           \
1894       else if (ch == 'N')                                         \
1895       {                                                           \
1896          if (action->add & bit)                                   \
1897          {                                                        \
1898             freez(action->string[index]);                         \
1899          }                                                        \
1900          action->add  &= ~bit;                                    \
1901          action->mask &= ~bit;                                    \
1902       }                                                           \
1903       else if (ch == 'X')                                         \
1904       {                                                           \
1905          if (action->add & bit)                                   \
1906          {                                                        \
1907             freez(action->string[index]);                         \
1908          }                                                        \
1909          action->add  &= ~bit;                                    \
1910          action->mask |= bit;                                     \
1911       }                                                           \
1912    }
1913
1914 #define DEFINE_ACTION_MULTI(name, index)                          \
1915    if (NULL != (param = lookup(parameters, name)))                \
1916    {                                                              \
1917       ch = toupper((int)param[0]);                                \
1918       if (ch == 'Y')                                              \
1919       {                                                           \
1920          /* FIXME */                                              \
1921       }                                                           \
1922       else if (ch == 'N')                                         \
1923       {                                                           \
1924          list_remove_all(action->multi_add[index]);               \
1925          list_remove_all(action->multi_remove[index]);            \
1926          action->multi_remove_all[index] = 1;                     \
1927       }                                                           \
1928       else if (ch == 'X')                                         \
1929       {                                                           \
1930          list_remove_all(action->multi_add[index]);               \
1931          list_remove_all(action->multi_remove[index]);            \
1932          action->multi_remove_all[index] = 0;                     \
1933       }                                                           \
1934    }
1935
1936 #define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val)
1937 #define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default)
1938 #define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val)
1939
1940 #define DEFINE_ACTION_ALIAS 0 /* No aliases for URL parsing */
1941
1942 #include "actionlist.h"
1943
1944 #undef DEFINE_ACTION_MULTI
1945 #undef DEFINE_ACTION_STRING
1946 #undef DEFINE_ACTION_BOOL
1947 #undef DEFINE_ACTION_ALIAS
1948 #undef DEFINE_CGI_PARAM_CUSTOM
1949 #undef DEFINE_CGI_PARAM_RADIO
1950 #undef DEFINE_CGI_PARAM_NO_RADIO
1951
1952    return JB_ERR_OK;
1953 }
1954
1955
1956 /*********************************************************************
1957  *
1958  * Function    :  cgi_edit_actions_submit
1959  *
1960  * Description :  CGI function that actually edits the Actions list.
1961  *
1962  * Parameters  :
1963  *           1 :  csp = Current client state (buffers, headers, etc...)
1964  *           2 :  rsp = http_response data structure for output
1965  *           3 :  parameters = map of cgi parameters
1966  *
1967  * CGI Parameters : None
1968  *
1969  * Returns     :  0 on success, nonzero on error
1970  *
1971  *********************************************************************/
1972 int cgi_edit_actions_submit(struct client_state *csp, struct http_response *rsp,
1973                             struct map *parameters)
1974 {
1975 /*
1976    struct map * exports;
1977 */
1978    int sectionid;
1979    char * actiontext;
1980    char * newtext;
1981    int len;
1982
1983    struct file_line * file;
1984    struct file_line * cur_line;
1985    int line_number;
1986    int rval;
1987    char * filename;
1988    const char * filename_param;
1989    char * target;
1990
1991    rval = get_file_name_param(csp, parameters, ".action", &filename, &filename_param);
1992    if (rval)
1993    {
1994       /* No filename specified. */
1995       /* FIXME: Shouldn't FATAL here */
1996       log_error(LOG_LEVEL_FATAL, "No filename specified");
1997       return 1;
1998    }
1999
2000    if (get_number_param(csp, parameters, "section", &sectionid))
2001    {
2002       /* FIXME: Shouldn't FATAL here */
2003       log_error(LOG_LEVEL_FATAL, "No 'section' parameter");
2004       return cgi_default(csp, rsp, parameters);
2005    }
2006
2007    if (edit_read_actions_file(filename, &file))
2008    {
2009       /* FIXME: Shouldn't FATAL here */
2010       log_error(LOG_LEVEL_FATAL, "Cannot load file '%s' for editing", filename);
2011       return 1;
2012    }
2013    cur_line = file;
2014
2015    for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
2016    {
2017       cur_line = cur_line->next;
2018    }
2019
2020    if ( (cur_line == NULL)
2021      || (line_number != sectionid)
2022      || (sectionid < 1)
2023      || (cur_line->type != FILE_LINE_ACTION))
2024    {
2025       edit_free_file(file);
2026       return cgi_default(csp, rsp, parameters);
2027    }
2028
2029    if (actions_from_radio(parameters, cur_line->data.action))
2030    {
2031       /* Out of memory */
2032       edit_free_file(file);
2033       return 1;
2034    }
2035
2036    if (NULL == (actiontext = actions_to_text(cur_line->data.action)))
2037    {
2038       /* Out of memory */
2039       edit_free_file(file);
2040       return 1;
2041    }
2042
2043    len = strlen(actiontext);
2044    if (NULL == (newtext = malloc(len + 2)))
2045    {
2046       /* Out of memory */
2047       free(actiontext);
2048       edit_free_file(file);
2049       return 1;
2050    }
2051    strcpy(newtext, actiontext);
2052    free(actiontext);
2053    newtext[0]       = '{';
2054    newtext[len]     = '}';
2055    newtext[len + 1] = '\0';
2056
2057    freez(cur_line->raw);
2058    freez(cur_line->unprocessed);
2059    cur_line->unprocessed = newtext;
2060
2061    if (edit_write_file(filename, file))
2062    {
2063       /* Error writing file */
2064       edit_free_file(file);
2065       /* FIXME: Shouldn't FATAL here */
2066       log_error(LOG_LEVEL_FATAL, "Cannot save file '%s' after editing", filename);
2067       return 1;
2068    }
2069
2070    edit_free_file(file);
2071    free(filename);
2072
2073
2074    target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
2075    string_append(&target, filename_param);
2076
2077    if (target == NULL)
2078    {
2079       /* Out of memory */
2080       /* FIXME: Shouldn't FATAL here */
2081       log_error(LOG_LEVEL_FATAL, "Out of memory");
2082       return 1;
2083    }
2084
2085    rsp->status = strdup("302 Local Redirect from Junkbuster");
2086    enlist_unique_header(rsp->headers, "Location", target);
2087    free(target);
2088
2089    return 0;
2090    /* return cgi_edit_actions_list(csp, rsp, parameters); */
2091 }
2092
2093
2094 #endif /* def FEATURE_CGI_EDIT_ACTIONS */
2095
2096
2097 /*
2098   Local Variables:
2099   tab-width: 3
2100   end:
2101 */