bcba42507a0b39de894bd18a83a3d01254e86d97
[privoxy.git] / cgiedit.c
1 const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.3 2001/10/14 22:12:49 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.3  2001/10/14 22:12:49  jongfoster
39  *    New version of CGI-based actionsfile editor.
40  *    Major changes, including:
41  *    - Completely new file parser and file output routines
42  *    - edit-actions CGI renamed edit-actions-for-url
43  *    - All CGIs now need a filename parameter, except for...
44  *    - New CGI edit-actions which doesn't need a filename,
45  *      to allow you to start the editor up.
46  *    - edit-actions-submit now works, and now automatically
47  *      redirects you back to the main edit-actions-list handler.
48  *
49  *    Revision 1.2  2001/09/16 17:05:14  jongfoster
50  *    Removing unused #include showarg.h
51  *
52  *    Revision 1.1  2001/09/16 15:47:37  jongfoster
53  *    First version of CGI-based edit interface.  This is very much a
54  *    work-in-progress, and you can't actually use it to edit anything
55  *    yet.  You must #define FEATURE_CGI_EDIT_ACTIONS for these changes
56  *    to have any effect.
57  *
58  *
59  **********************************************************************/
60 \f
61
62 #include "config.h"
63
64 /*
65  * FIXME: Following includes copied from cgi.c - which are actually needed?
66  */
67
68 #include <stdio.h>
69 #include <stdlib.h>
70 #include <sys/types.h>
71 #include <stdlib.h>
72 #include <ctype.h>
73 #include <string.h>
74 #include <assert.h>
75 #include <limits.h>
76 #include <sys/stat.h>
77
78 #ifdef _WIN32
79 #define snprintf _snprintf
80 #endif /* def _WIN32 */
81
82 #include "project.h"
83 #include "cgi.h"
84 #include "cgiedit.h"
85 #include "cgisimple.h"
86 #include "list.h"
87 #include "encode.h"
88 #include "actions.h"
89 #include "miscutil.h"
90 #include "errlog.h"
91 #include "loadcfg.h"
92 /* loadcfg.h is for g_bToggleIJB only */
93
94 const char cgiedit_h_rcs[] = CGIEDIT_H_VERSION;
95
96
97 #ifdef FEATURE_CGI_EDIT_ACTIONS
98
99 struct file_line
100 {
101    struct file_line * next;
102    char * raw;
103    char * prefix;
104    char * unprocessed;
105    int type;
106    
107    union
108    {
109       struct action_spec action[1];
110
111       struct
112       {
113          char * name;
114          char * svalue;
115          int ivalue;
116       } setting;
117
118       /* Add more data types here... e.g.
119
120
121       struct url_spec url[1];
122
123       struct
124       {
125          struct action_spec action[1];
126          const char * name;
127       } alias;
128
129       */
130
131    } data;
132 };
133
134 #define FILE_LINE_UNPROCESSED           1
135 #define FILE_LINE_BLANK                 2
136 #define FILE_LINE_ALIAS_HEADER          3
137 #define FILE_LINE_ALIAS_ENTRY           4
138 #define FILE_LINE_ACTION                5
139 #define FILE_LINE_URL                   6
140 #define FILE_LINE_SETTINGS_HEADER       7
141 #define FILE_LINE_SETTINGS_ENTRY        8
142 #define FILE_LINE_DESCRIPTION_HEADER    9
143 #define FILE_LINE_DESCRIPTION_ENTRY    10
144
145
146 struct editable_file
147 {
148    struct file_line * lines;
149    const char * filename;     /* Full pathname - e.g. "/etc/junkbuster/wibble.action" */
150    const char * identifier;   /* Filename stub - e.g. "wibble".  Use for CGI param. */
151    const char * version_str;  /* Last modification time, as a string.  For CGI param */
152    unsigned version;          /* Last modification time - prevents chaos with
153                                * the browser's "back" button.  Note that this is a
154                                * time_t cast to an unsigned.  When comparing, always
155                                * cast the time_t to an unsigned, and *NOT* vice-versa.
156                                * This may lose the top few bits, but they're not
157                                * significant anyway.
158                                */
159    struct file_line * parse_error; /* On parse error, this is the offending line. */
160    const char * parse_error_text;  /* On parse error, this is the problem.  
161                                     * (Statically allocated) */
162 };
163
164 /* FIXME: Following non-static functions should be prototyped in .h or made static */
165
166 /* Functions to read and write arbitrary config files */
167 jb_err edit_read_file(struct client_state *csp,
168                       const struct map *parameters,
169                       int require_version,
170                       const char *suffix,
171                       struct editable_file **pfile);
172 jb_err edit_write_file(struct editable_file * file);
173 void   edit_free_file(struct editable_file * file);
174
175 /* Functions to read and write actions files */
176 jb_err edit_parse_actions_file(struct editable_file * file);
177 jb_err edit_read_actions_file(struct client_state *csp,
178                               struct http_response *rsp,
179                               const struct map *parameters,
180                               int require_version,
181                               struct editable_file **pfile);
182
183 /* Error handlers */
184 jb_err cgi_error_modified(struct client_state *csp,
185                           struct http_response *rsp,
186                           const char *filename);
187 jb_err cgi_error_parse(struct client_state *csp,
188                        struct http_response *rsp,
189                        struct editable_file *file);
190 jb_err cgi_error_file(struct client_state *csp,
191                       struct http_response *rsp,
192                       const char *filename);
193
194 /* Internal arbitrary config file support functions */
195 static jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile);
196 static void edit_free_file_lines(struct file_line * first_line);
197 static jb_err simple_read_line(char **dest, FILE *fp);
198 static jb_err edit_read_line(FILE *fp, char **raw_out, char **prefix_out, char **data_out);
199
200 /* Internal actions file support functions */
201 static int match_actions_file_header_line(const char * line, const char * name);
202 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue);
203
204 /* Internal parameter parsing functions */
205 static jb_err get_file_name_param(struct client_state *csp,
206                                   const struct map *parameters,
207                                   const char *param_name,
208                                   const char *suffix,
209                                   char **pfilename,
210                                   const char **pparam);
211 static jb_err get_number_param(struct client_state *csp,
212                                const struct map *parameters,
213                                char *name,
214                                unsigned *pvalue);
215
216 /* Internal actionsfile <==> HTML conversion functions */
217 static jb_err map_radio(struct map * exports,
218                         const char * optionname, 
219                         const char * values,
220                         char value);
221 static jb_err actions_to_radio(struct map * exports,
222                                const struct action_spec *action);
223 static jb_err actions_from_radio(const struct map * parameters,
224                                  struct action_spec *action);
225
226
227 /*********************************************************************
228  *
229  * Function    :  simple_read_line
230  *
231  * Description :  Read a single line from a file and return it.
232  *                This is basically a version of fgets() that malloc()s
233  *                it's own line buffer.  Note that the buffer will
234  *                always be a multiple of BUFFER_SIZE bytes long.
235  *                Therefore if you are going to keep the string for
236  *                an extended period of time, you should probably
237  *                strdup() it and free() the original, to save memory.
238  *
239  *
240  * Parameters  :
241  *          1  :  dest = destination for newly malloc'd pointer to
242  *                line data.  Will be set to NULL on error.
243  *          2  :  fp = File to read from
244  *
245  * Returns     :  JB_ERR_OK     on success
246  *                JB_ERR_MEMORY on out-of-memory
247  *                JB_ERR_FILE   on EOF.
248  *
249  *********************************************************************/
250 static jb_err simple_read_line(char **dest, FILE *fp)
251 {
252    int len;
253    char * buf;
254    char * newbuf;
255
256    assert(fp);
257    assert(dest);
258
259    *dest = NULL;
260
261    if (NULL == (buf = malloc(BUFFER_SIZE)))
262    {
263       return JB_ERR_MEMORY;
264    }
265    
266    *buf = '\0';
267    len = 0;
268
269    while (FOREVER)
270    {
271       newbuf = buf + len;
272       if ((!fgets(newbuf, BUFFER_SIZE, fp)) || (*newbuf == '\0'))
273       {
274          /* (*newbuf == '\0') should never happen unless fgets fails */
275          if (*buf == '\0')
276          {
277             free(buf);
278             return JB_ERR_FILE;
279          }
280          else
281          {
282             *dest = buf;
283             return JB_ERR_OK;
284          }
285       }
286       len = strlen(buf);
287       if ((buf[len - 1] == '\n') || (buf[len - 1] == '\r'))
288       {
289          *dest = buf;
290          return JB_ERR_OK;
291       }
292       
293       if (NULL == (newbuf = realloc(buf, len + BUFFER_SIZE)))
294       {
295          free(buf);
296          return JB_ERR_MEMORY;
297       }
298       buf = newbuf;
299    }
300 }
301
302
303 /*********************************************************************
304  *
305  * Function    :  edit_read_line
306  *
307  * Description :  Read a single non-empty line from a file and return
308  *                it.  Trims comments, leading and trailing whitespace
309  *                and respects escaping of newline and comment char.
310  *                Provides the line in 2 alternative forms: raw and
311  *                preprocessed.
312  *                - raw is the raw data read from the file.  If the 
313  *                  line is not modified, then this should be written
314  *                  to the new file.
315  *                - prefix is any comments and blank lines that were
316  *                  read from the file.  If the line is modified, then
317  *                  this should be written out to the file followed
318  *                  by the modified data.  (If this string is non-empty
319  *                  then it will have a newline at the end).
320  *                - data is the actual data that will be parsed
321  *                  further by appropriate routines.
322  *                On EOF, the 3 strings will all be set to NULL and
323  *                0 will be returned.
324  *
325  * Parameters  :
326  *          1  :  fp = File to read from
327  *          2  :  raw_out = destination for newly malloc'd pointer to
328  *                raw line data.  May be NULL if you don't want it.
329  *          3  :  prefix_out = destination for newly malloc'd pointer to
330  *                comments.  May be NULL if you don't want it.
331  *          4  :  data_out = destination for newly malloc'd pointer to
332  *                line data with comments and leading/trailing spaces
333  *                removed, and line continuation performed.  May be
334  *                NULL if you don't want it.
335  *
336  * Returns     :  JB_ERR_OK     on success
337  *                JB_ERR_MEMORY on out-of-memory
338  *                JB_ERR_FILE   on EOF.
339  *
340  *********************************************************************/
341 static jb_err edit_read_line(FILE *fp, char **raw_out, char **prefix_out, char **data_out)
342 {
343    char *p;          /* Temporary pointer   */
344    char *linebuf;    /* Line read from file */
345    char *linestart;  /* Start of linebuf, usually first non-whitespace char */
346    char newline[3];  /* Used to store the newline - "\n", "\r", or "\r\n"   */
347    int contflag = 0; /* Nonzero for line continuation - i.e. line ends '\'  */
348    char *raw;        /* String to be stored in raw_out    */
349    char *prefix;     /* String to be stored in prefix_out */
350    char *data;       /* String to be stored in data_out   */
351    jb_err rval = JB_ERR_OK;
352
353    assert(fp);
354
355    /* Set output parameters to NULL */
356    if (raw_out)
357    {
358       *raw_out    = NULL;
359    }
360    if (prefix_out)
361    {
362       *prefix_out = NULL;
363    }
364    if (data_out)
365    {
366       *data_out   = NULL;
367    }
368
369    /* Set string variables to new, empty strings. */
370
371    raw    = malloc(1);
372    prefix = malloc(1);
373    data   = malloc(1);
374
375    if ((raw == NULL) || (prefix == NULL) || (data == NULL))
376    {
377       freez(raw);
378       freez(prefix);
379       freez(data);
380       return JB_ERR_MEMORY;
381    }
382
383    *raw    = '\0';
384    *prefix = '\0';
385    *data   = '\0';
386
387    /* Main loop.  Loop while we need more data & it's not EOF. */
388
389    while ( (contflag || (*data == '\0'))
390         && (JB_ERR_OK == (rval = simple_read_line(&linebuf, fp))))
391    {
392       if (string_append(&raw,linebuf))
393       {
394          free(prefix);
395          free(data);
396          free(linebuf);
397          return JB_ERR_MEMORY;
398       }
399       
400       /* Trim off newline */
401       p = linebuf + strlen(linebuf);
402       if ((p != linebuf) && ((p[-1] == '\r') || (p[-1] == '\n')))
403       {
404          p--;
405          if ((p != linebuf) && ((p[-1] == '\r') || (p[-1] == '\n')))
406          {
407             p--;
408          }
409       }
410       strcpy(newline, p);
411       *p = '\0';
412
413       /* Line continuation? Trim escape and set flag. */
414       contflag = ((p != linebuf) && (*--p == '\\'));
415       if (contflag)
416       {
417          *p = '\0';
418       }
419
420       /* Trim leading spaces if we're at the start of the line */
421       linestart = linebuf;
422       if (*data == '\0')
423       {
424          /* Trim leading spaces */
425          while (*linestart && isspace((int)(unsigned char)*linestart))
426          {
427             linestart++;
428          }
429       }
430
431       /* Handle comment characters. */
432       p = linestart;
433       while ((p = strchr(p, '#')) != NULL)
434       {
435          /* Found a comment char.. */
436          if ((p != linebuf) && (*(p-1) == '\\'))
437          {
438             /* ..and it's escaped, left-shift the line over the escape. */
439             char *q = p - 1;
440             while ((*q = *(q + 1)) != '\0')
441             {
442                q++;
443             }
444             /* Now scan from just after the "#". */
445          }
446          else
447          {
448             /* Real comment.  Save it... */
449             if (p == linestart)
450             {
451                /* Special case:  Line only contains a comment, so all the
452                 * previous whitespace is considered part of the comment.
453                 * Undo the whitespace skipping, if any.
454                 */
455                linestart = linebuf;
456                p = linestart;
457             }
458             string_append(&prefix,p);
459             if (string_append(&prefix,newline))
460             {
461                free(raw);
462                free(data);
463                free(linebuf);
464                return JB_ERR_MEMORY;
465             }
466             *newline = '\0';
467
468             /* ... and chop off the rest of the line */
469             *p = '\0';
470          }
471       } /* END while (there's a # character) */
472
473       /* Write to the buffer */
474       if (*linestart)
475       {
476          if (string_append(&data, linestart))
477          {
478             free(raw);
479             free(prefix);
480             free(linebuf);
481             return JB_ERR_MEMORY;
482          }
483       }
484
485       free(linebuf);
486    } /* END while(we need more data) */
487
488    /* Handle simple_read_line() errors - ignore EOF */
489    if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
490    {
491       free(raw);
492       free(prefix);
493       free(data);
494       return rval;
495    }
496
497
498    if (*raw)
499    {
500       /* Got at least some data */
501
502       /* Remove trailing whitespace */         
503       chomp(data);
504
505       if (raw_out)
506       {
507          *raw_out    = raw;
508       }
509       else
510       {
511          free(raw);
512       }
513       if (prefix_out)
514       {
515          *prefix_out = prefix;
516       }
517       else
518       {
519          free(prefix);
520       }
521       if (data_out)
522       {
523          *data_out   = data;
524       }
525       else
526       {
527          free(data);
528       }
529       return JB_ERR_OK;
530    }
531    else
532    {
533       /* EOF and no data there. */
534
535       free(raw);
536       free(prefix);
537       free(data);
538
539       return JB_ERR_FILE;
540    }
541 }
542
543
544 /*********************************************************************
545  *
546  * Function    :  edit_write_file
547  *
548  * Description :  Write a complete file to disk.
549  *
550  * Parameters  :
551  *          1  :  filename = File to write to.
552  *          2  :  file = Data structure to write.
553  *
554  * Returns     :  JB_ERR_OK     on success
555  *                JB_ERR_FILE   on error writing to file.
556  *                JB_ERR_MEMORY on out of memory
557  *
558  *********************************************************************/
559 jb_err edit_write_file(struct editable_file * file)
560 {
561    FILE * fp;
562    struct file_line * cur_line;
563    struct stat statbuf[1];
564    char version_buf[22]; /* 22 = ceil(log10(2^64)) + 2 = max number of
565                             digits in time_t, assuming this is a 64-bit
566                             machine, plus null terminator, plus one
567                             for paranoia */
568
569    assert(file);
570    assert(file->filename);
571
572    if (NULL == (fp = fopen(file->filename, "wt")))
573    {
574       return JB_ERR_FILE;
575    }
576
577    cur_line = file->lines;
578    while (cur_line != NULL)
579    {
580       if (cur_line->raw)
581       {
582          if (fputs(cur_line->raw, fp) < 0)
583          {
584             fclose(fp);
585             return JB_ERR_FILE;
586          }
587       }
588       else
589       {
590          if (cur_line->prefix)
591          {
592             if (fputs(cur_line->prefix, fp) < 0)
593             {
594                fclose(fp);
595                return JB_ERR_FILE;
596             }
597          }
598          if (cur_line->unprocessed)
599          {
600             if (fputs(cur_line->unprocessed, fp) < 0)
601             {
602                fclose(fp);
603                return JB_ERR_FILE;
604             }
605             if (fputs("\n", fp) < 0)
606             {
607                fclose(fp);
608                return JB_ERR_FILE;
609             }
610          }
611          else
612          {
613             /* FIXME: Write data from file->data->whatever */
614             assert(0);
615          }
616       }
617       cur_line = cur_line->next;
618    }
619
620    fclose(fp);
621
622
623    /* Update the version stamp in the file structure, since we just
624     * wrote to the file & changed it's date.
625     */
626    if (stat(file->filename, statbuf) < 0)
627    {
628       /* Error, probably file not found. */
629       return JB_ERR_FILE;
630    }
631    file->version = (unsigned)statbuf->st_mtime;
632
633    /* Correct file->version_str */
634    freez((char *)file->version_str);
635    snprintf(version_buf, 22, "%u", file->version);
636    version_buf[21] = '\0';
637    file->version_str = strdup(version_buf);
638    if (version_buf == NULL)
639    {
640       return JB_ERR_MEMORY;
641    }
642
643    return JB_ERR_OK;
644 }
645
646
647 /*********************************************************************
648  *
649  * Function    :  edit_free_file
650  *
651  * Description :  Free a complete file in memory.  
652  *
653  * Parameters  :
654  *          1  :  file = Data structure to free.
655  *
656  * Returns     :  N/A
657  *
658  *********************************************************************/
659 void edit_free_file(struct editable_file * file)
660 {
661    if (!file)
662    {
663       /* Silently ignore NULL pointer */
664       return;
665    }
666
667    edit_free_file_lines(file->lines);
668    freez((char *)file->filename);
669    freez((char *)file->identifier);
670    freez((char *)file->version_str);
671    file->version = 0;
672    file->parse_error_text = NULL; /* Statically allocated */
673    file->parse_error = NULL;
674
675    free(file);
676 }
677
678
679 /*********************************************************************
680  *
681  * Function    :  edit_free_file
682  *
683  * Description :  Free an entire linked list of file lines.  
684  *
685  * Parameters  :
686  *          1  :  first_line = Data structure to free.
687  *
688  * Returns     :  N/A
689  *
690  *********************************************************************/
691 static void edit_free_file_lines(struct file_line * first_line)
692 {
693    struct file_line * next_line;
694
695    while (first_line != NULL)
696    {
697       next_line = first_line->next;
698       first_line->next = NULL;
699       freez(first_line->raw);
700       freez(first_line->prefix);
701       freez(first_line->unprocessed);
702       switch(first_line->type)
703       {
704          case 0: /* special case if memory zeroed */
705          case FILE_LINE_UNPROCESSED:
706          case FILE_LINE_BLANK:
707          case FILE_LINE_ALIAS_HEADER:
708          case FILE_LINE_SETTINGS_HEADER:
709          case FILE_LINE_DESCRIPTION_HEADER:
710          case FILE_LINE_DESCRIPTION_ENTRY:
711          case FILE_LINE_ALIAS_ENTRY:
712          case FILE_LINE_URL:
713             /* No data is stored for these */
714             break;
715
716          case FILE_LINE_ACTION:
717             free_action(first_line->data.action);
718             break;
719
720          case FILE_LINE_SETTINGS_ENTRY:
721             freez(first_line->data.setting.name);
722             freez(first_line->data.setting.svalue);
723             break;
724          default:
725             /* Should never happen */
726             assert(0);
727             break;
728       }
729       first_line->type = 0; /* paranoia */
730       free(first_line);
731       first_line = next_line;
732    }
733 }
734
735
736 /*********************************************************************
737  *
738  * Function    :  match_actions_file_header_line
739  *
740  * Description :  Match an actions file {{header}} line 
741  *
742  * Parameters  :
743  *          1  :  line - String from file
744  *          2  :  name - Header to match against
745  *
746  * Returns     :  0 iff they match.
747  *
748  *********************************************************************/
749 static int match_actions_file_header_line(const char * line, const char * name)
750 {
751    int len;
752
753    assert(line);
754    assert(name);
755
756    /* Look for "{{" */
757    if ((line[0] != '{') || (line[1] != '{'))
758    {
759       return 1;
760    }
761    line += 2;
762
763    /* Look for optional whitespace */
764    while ( (*line == ' ') || (*line == '\t') )
765    {
766       line++;
767    }
768
769    /* Look for the specified name (case-insensitive) */
770    len = strlen(name);
771    if (0 != strncmpic(line, name, len))
772    {
773       return 1;
774    }
775    line += len;
776
777    /* Look for optional whitespace */
778    while ( (*line == ' ') || (*line == '\t') )
779    {
780       line++;
781    }
782
783    /* Look for "}}" and end of string*/
784    if ((line[0] != '}') || (line[1] != '}') || (line[2] != '\0'))
785    {
786       return 1;
787    }
788
789    /* It matched!! */
790    return 0;
791 }
792
793
794 /*********************************************************************
795  *
796  * Function    :  match_actions_file_header_line
797  *
798  * Description :  Match an actions file {{header}} line 
799  *
800  * Parameters  :
801  *          1  :  line - String from file.  Must not start with
802  *                       whitespace (else infinite loop!)
803  *          2  :  name - Destination for name
804  *          2  :  name - Destination for value
805  *
806  * Returns     :  JB_ERR_OK     on success
807  *                JB_ERR_MEMORY on out-of-memory
808  *                JB_ERR_PARSE  if there's no "=" sign, or if there's
809  *                              nothing before the "=" sign (but empty
810  *                              values *after* the "=" sign are legal).
811  *
812  *********************************************************************/
813 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue)
814 {
815    const char * name_end;
816    const char * value_start;
817    int name_len;
818
819    assert(line);
820    assert(pname);
821    assert(pvalue);
822    assert(*line != ' ');
823    assert(*line != '\t');
824
825    *pname = NULL;
826    *pvalue = NULL;
827
828    value_start = strchr(line, '=');
829    if ((value_start == NULL) || (value_start == line))
830    {
831       return JB_ERR_PARSE;
832    }
833
834    name_end = value_start - 1;
835
836    /* Eat any whitespace before the '=' */
837    while ((*name_end == ' ') || (*name_end == '\t'))
838    {
839       /*
840        * we already know we must have at least 1 non-ws char
841        * at start of buf - no need to check
842        */
843       name_end--;
844    }
845
846    name_len = name_end - line + 1; /* Length excluding \0 */
847    if (NULL == (*pname = (char *) malloc(name_len + 1)))
848    {
849       return JB_ERR_MEMORY;
850    }
851    strncpy(*pname, line, name_len);
852    (*pname)[name_len] = '\0';
853
854    /* Eat any the whitespace after the '=' */
855    value_start++;
856    while ((*value_start == ' ') || (*value_start == '\t'))
857    {
858       value_start++;
859    }
860
861    if (NULL == (*pvalue = strdup(value_start)))
862    {
863       free(*pname);
864       *pname = NULL;
865       return JB_ERR_MEMORY;
866    }
867
868    return JB_ERR_OK;
869 }
870
871
872 /*********************************************************************
873  *
874  * Function    :  edit_parse_actions_file
875  *
876  * Description :  Parse an actions file in memory.  
877  *
878  *                Passed linked list must have the "data" member
879  *                zeroed, and must contain valid "next" and
880  *                "unprocessed" fields.  The "raw" and "prefix"
881  *                fields are ignored, and "type" is just overwritten.
882  *
883  *                Note that on error the file may have been
884  *                partially parsed.
885  *
886  * Parameters  :
887  *          1  :  file = Actions file to be parsed in-place.
888  *
889  * Returns     :  JB_ERR_OK     on success
890  *                JB_ERR_MEMORY on out-of-memory
891  *                JB_ERR_PARSE  on error
892  *
893  *********************************************************************/
894 jb_err edit_parse_actions_file(struct editable_file * file)
895 {
896    struct file_line * cur_line;
897    int len;
898    const char * text; /* Text from a line */
899    char * name;  /* For lines of the form name=value */
900    char * value; /* For lines of the form name=value */
901    struct action_alias * alias_list = NULL;
902    jb_err err = JB_ERR_OK;
903
904    /* alias_list contains the aliases defined in this file.
905     * It might be better to use the "file_line.data" fields
906     * in the relavent places instead.
907     */
908
909    cur_line = file->lines;
910
911    /* A note about blank line support: Blank lines should only 
912     * ever occur as the last line in the file.  This function
913     * is more forgiving than that - FILE_LINE_BLANK can occur
914     * anywhere.
915     */
916
917    /* Skip leading blanks.  Should only happen if file is
918     * empty (which is valid, but pointless).
919     */
920    while ( (cur_line != NULL)
921         && (cur_line->unprocessed[0] == '\0') )
922    {
923       /* Blank line */
924       cur_line->type = FILE_LINE_BLANK;
925       cur_line = cur_line->next;
926    }
927
928    if ( (cur_line != NULL)
929      && (cur_line->unprocessed[0] != '{') )
930    {
931       /* File doesn't start with a header */
932       file->parse_error = cur_line;
933       file->parse_error_text = "First (non-comment) line of the file must contain a header.";
934       return JB_ERR_PARSE;
935    }
936
937    if ( (cur_line != NULL) && (0 ==
938       match_actions_file_header_line(cur_line->unprocessed, "settings") ) )
939    {
940       cur_line->type = FILE_LINE_SETTINGS_HEADER;
941
942       cur_line = cur_line->next;
943       while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
944       {
945          if (cur_line->unprocessed[0])
946          {
947             cur_line->type = FILE_LINE_SETTINGS_ENTRY;
948
949             err = split_line_on_equals(cur_line->unprocessed,
950                      &cur_line->data.setting.name,
951                      &cur_line->data.setting.svalue);
952             if (err == JB_ERR_MEMORY)
953             {
954                return err;
955             }
956             else if (err != JB_ERR_OK)
957             {
958                /* Line does not contain a name=value pair */
959                file->parse_error = cur_line;
960                file->parse_error_text = "Expected a name=value pair on this {{description}} line, but couldn't find one.";
961                return JB_ERR_PARSE;
962             }
963          }
964          else
965          {
966             cur_line->type = FILE_LINE_BLANK;
967          }
968          cur_line = cur_line->next;
969       }
970    }
971
972    if ( (cur_line != NULL) && (0 ==
973       match_actions_file_header_line(cur_line->unprocessed, "description") ) )
974    {
975       cur_line->type = FILE_LINE_DESCRIPTION_HEADER;
976
977       cur_line = cur_line->next;
978       while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
979       {
980          if (cur_line->unprocessed[0])
981          {
982             cur_line->type = FILE_LINE_DESCRIPTION_ENTRY;
983          }
984          else
985          {
986             cur_line->type = FILE_LINE_BLANK;
987          }
988          cur_line = cur_line->next;
989       }
990    }
991
992    if ( (cur_line != NULL) && (0 ==
993       match_actions_file_header_line(cur_line->unprocessed, "alias") ) )
994    {
995       cur_line->type = FILE_LINE_ALIAS_HEADER;
996
997       cur_line = cur_line->next;
998       while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
999       {
1000          if (cur_line->unprocessed[0])
1001          {
1002             /* define an alias */
1003             struct action_alias * new_alias;
1004
1005             cur_line->type = FILE_LINE_ALIAS_ENTRY;
1006
1007             err = split_line_on_equals(cur_line->unprocessed, &name, &value);
1008             if (err == JB_ERR_MEMORY)
1009             {
1010                return err;
1011             }
1012             else if (err != JB_ERR_OK)
1013             {
1014                /* Line does not contain a name=value pair */
1015                file->parse_error = cur_line;
1016                file->parse_error_text = "Expected a name=value pair on this {{alias}} line, but couldn't find one.";
1017                return JB_ERR_PARSE;
1018             }
1019
1020             if ((new_alias = zalloc(sizeof(*new_alias))) == NULL)
1021             {
1022                /* Out of memory */
1023                free(name);
1024                free(value);
1025                free_alias_list(alias_list);
1026                return JB_ERR_MEMORY;
1027             }
1028
1029             err = get_actions(value, alias_list, new_alias->action);
1030             if (err)
1031             {
1032                /* Invalid action or out of memory */
1033                free(name);
1034                free(value);
1035                free(new_alias);
1036                free_alias_list(alias_list);
1037                if (err == JB_ERR_MEMORY)
1038                {
1039                   return err;
1040                }
1041                else
1042                {
1043                   /* Line does not contain a name=value pair */
1044                   file->parse_error = cur_line;
1045                   file->parse_error_text = "This alias does not specify a valid set of actions.";
1046                   return JB_ERR_PARSE;
1047                }
1048             }
1049
1050             free(value);
1051
1052             new_alias->name = name;
1053
1054             /* add to list */
1055             new_alias->next = alias_list;
1056             alias_list = new_alias;
1057          }
1058          else
1059          {
1060             cur_line->type = FILE_LINE_BLANK;
1061          }
1062          cur_line = cur_line->next;
1063       }
1064    }
1065
1066    /* Header done, process the main part of the file */
1067    while (cur_line != NULL)
1068    {
1069       /* At this point, (cur_line->unprocessed[0] == '{') */
1070       assert(cur_line->unprocessed[0] == '{');
1071       text = cur_line->unprocessed + 1;
1072       len = strlen(text) - 1;
1073       if (text[len] != '}')
1074       {
1075          /* No closing } on header */
1076          free_alias_list(alias_list);
1077          file->parse_error = cur_line;
1078          file->parse_error_text = "Headers starting with '{' must have a "
1079             "closing bracket ('}').  Headers starting with two brackets ('{{') "
1080             "must close with two brackets ('}}').";
1081          return JB_ERR_PARSE;
1082       }
1083
1084       if (text[0] == '{')
1085       {
1086          /* An invalid {{ header.  */
1087          free_alias_list(alias_list);
1088          file->parse_error = cur_line;
1089          file->parse_error_text = "Unknown or unexpected two-bracket header.  "
1090             "Please remember that the system (two-bracket) headers must "
1091             "appear in the order {{settings}}, {{description}}, {{alias}}, "
1092             "and must appear before any actions (one-bracket) headers.  "
1093             "Also note that system headers may not be repeated.";
1094          return JB_ERR_PARSE;
1095       }
1096
1097       while ( (*text == ' ') || (*text == '\t') )
1098       {
1099          text++;
1100          len--;
1101       }
1102       while ( (len > 0)
1103            && ( (text[len - 1] == ' ')
1104              || (text[len - 1] == '\t') ) )
1105       {
1106          len--;
1107       }
1108
1109       cur_line->type = FILE_LINE_ACTION;
1110
1111       /* Remove {} and make copy */
1112       if (NULL == (value = (char *) malloc(len + 1)))
1113       {
1114          /* Out of memory */
1115          free_alias_list(alias_list);
1116          return JB_ERR_MEMORY;
1117       }
1118       strncpy(value, text, len);
1119       value[len] = '\0';
1120
1121       /* Get actions */
1122       err = get_actions(value, alias_list, cur_line->data.action);
1123       if (err)
1124       {
1125          /* Invalid action or out of memory */
1126          free(value);
1127          free_alias_list(alias_list);
1128          if (err == JB_ERR_MEMORY)
1129          {
1130             return err;
1131          }
1132          else
1133          {
1134             /* Line does not contain a name=value pair */
1135             file->parse_error = cur_line;
1136             file->parse_error_text = "This header does not specify a valid set of actions.";
1137             return JB_ERR_PARSE;
1138          }
1139       }
1140
1141       /* Done with string - it was clobbered anyway */
1142       free(value);
1143
1144       /* Process next line */
1145       cur_line = cur_line->next;
1146
1147       /* Loop processing URL patterns */
1148       while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1149       {
1150          if (cur_line->unprocessed[0])
1151          {
1152             /* Could parse URL here, but this isn't currently needed */
1153
1154             cur_line->type = FILE_LINE_URL;
1155          }
1156          else
1157          {
1158             cur_line->type = FILE_LINE_BLANK;
1159          }
1160          cur_line = cur_line->next;
1161       }
1162    } /* End main while(cur_line != NULL) loop */
1163
1164    free_alias_list(alias_list);
1165
1166    return JB_ERR_OK;
1167 }
1168
1169
1170 /*********************************************************************
1171  *
1172  * Function    :  edit_read_file_lines
1173  *
1174  * Description :  Read all the lines of a file into memory.  
1175  *                Handles whitespace, comments and line continuation.
1176  *
1177  * Parameters  :
1178  *          1  :  fp = File to read from.  On return, this will be
1179  *                     at EOF but it will not have been closed.
1180  *          2  :  pfile = Destination for a linked list of file_lines.
1181  *                        Will be set to NULL on error.
1182  *
1183  * Returns     :  JB_ERR_OK     on success
1184  *                JB_ERR_MEMORY on out-of-memory
1185  *
1186  *********************************************************************/
1187 jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile)
1188 {
1189    struct file_line * first_line; /* Keep for return value or to free */
1190    struct file_line * cur_line;   /* Current line */
1191    struct file_line * prev_line;  /* Entry with prev_line->next = cur_line */
1192    jb_err rval;
1193
1194    assert(fp);
1195    assert(pfile);
1196
1197    *pfile = NULL;
1198
1199    cur_line = first_line = zalloc(sizeof(struct file_line));
1200    if (cur_line == NULL)
1201    {
1202       return JB_ERR_MEMORY;
1203    }
1204
1205    cur_line->type = FILE_LINE_UNPROCESSED;
1206
1207    rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed);
1208    if (rval)
1209    {
1210       /* Out of memory or empty file. */
1211       /* Note that empty file is not an error we propogate up */
1212       free(cur_line);
1213       return ((rval == JB_ERR_FILE) ? JB_ERR_OK : rval);
1214    }
1215
1216    do
1217    {
1218       prev_line = cur_line;
1219       cur_line = prev_line->next = zalloc(sizeof(struct file_line));
1220       if (cur_line == NULL)
1221       {
1222          /* Out of memory */
1223          edit_free_file_lines(first_line);
1224          return JB_ERR_MEMORY;
1225       }
1226
1227       cur_line->type = FILE_LINE_UNPROCESSED;
1228
1229       rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed);
1230       if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
1231       {
1232          /* Out of memory */
1233          edit_free_file_lines(first_line);
1234          return JB_ERR_MEMORY;
1235       }
1236
1237    }
1238    while (rval != JB_ERR_FILE);
1239
1240    /* EOF */
1241
1242    /* We allocated one too many - free it */
1243    prev_line->next = NULL;
1244    free(cur_line);
1245
1246    *pfile = first_line;
1247    return JB_ERR_OK;
1248 }
1249
1250
1251 /*********************************************************************
1252  *
1253  * Function    :  edit_read_file
1254  *
1255  * Description :  Read a complete file into memory.
1256  *                Handles CGI parameter parsing.  If requested, also
1257  *                checks the file's modification timestamp.
1258  *
1259  * Parameters  :
1260  *          1  :  csp = Current client state (buffers, headers, etc...)
1261  *          2  :  parameters = map of cgi parameters.
1262  *          3  :  require_version = true to check "ver" parameter.
1263  *          4  :  suffix = File extension, e.g. ".action".
1264  *          5  :  pfile = Destination for the file.  Will be set
1265  *                        to NULL on error.
1266  *
1267  * CGI Parameters :
1268  *    filename :  The name of the file to read, without the
1269  *                path or ".action" extension.
1270  *         ver :  (Only if require_version is nonzero)
1271  *                Timestamp of the actions file.  If wrong, this
1272  *                function fails with JB_ERR_MODIFIED.
1273  *
1274  * Returns     :  JB_ERR_OK     on success
1275  *                JB_ERR_MEMORY on out-of-memory
1276  *                JB_ERR_CGI_PARAMS if "filename" was not specified
1277  *                                  or is not valid.
1278  *                JB_ERR_FILE   if the file cannot be opened or
1279  *                              contains no data
1280  *                JB_ERR_MODIFIED if version checking was requested and
1281  *                                failed - the file was modified outside
1282  *                                of this CGI editor instance.
1283  *
1284  *********************************************************************/
1285 jb_err edit_read_file(struct client_state *csp,
1286                       const struct map *parameters,
1287                       int require_version,
1288                       const char *suffix,
1289                       struct editable_file **pfile)
1290 {
1291    struct file_line * lines;
1292    FILE * fp;
1293    jb_err err;
1294    char * filename;
1295    const char * identifier;
1296    struct editable_file * file;
1297    unsigned version = 0;
1298    struct stat statbuf[1];
1299    char version_buf[22];
1300
1301    assert(csp);
1302    assert(parameters);
1303    assert(pfile);
1304
1305    *pfile = NULL;
1306
1307    err = get_file_name_param(csp, parameters, "filename", suffix,
1308                              &filename, &identifier);
1309    if (err)
1310    {
1311       return err;
1312    }
1313
1314    if (stat(filename, statbuf) < 0)
1315    {
1316       /* Error, probably file not found. */
1317       free(filename);
1318       return JB_ERR_FILE;
1319    }
1320    version = (unsigned) statbuf->st_mtime;
1321
1322    if (require_version)
1323    {
1324       unsigned specified_version;
1325       err = get_number_param(csp, parameters, "ver", &specified_version);
1326       if (err)
1327       {
1328          free(filename);
1329          return err;
1330       }
1331
1332       if (version != specified_version)
1333       {
1334          return JB_ERR_MODIFIED;
1335       }
1336    }
1337
1338    if (NULL == (fp = fopen(filename,"rt")))
1339    {
1340       free(filename);
1341       return JB_ERR_FILE;
1342    }
1343
1344    err = edit_read_file_lines(fp, &lines);
1345
1346    fclose(fp);
1347
1348    if (err)
1349    {
1350       free(filename);
1351       return err;
1352    }
1353
1354    file = (struct editable_file *) zalloc(sizeof(*file));
1355    if (err)
1356    {
1357       free(filename);
1358       edit_free_file_lines(lines);
1359       return err;
1360    }
1361
1362    file->lines = lines;
1363    file->filename = filename;
1364    file->version = version;
1365    file->identifier = strdup(identifier);
1366
1367    if (file->identifier == NULL)
1368    {
1369       edit_free_file(file);
1370       return JB_ERR_MEMORY;
1371    }
1372
1373    /* Correct file->version_str */
1374    freez((char *)file->version_str);
1375    snprintf(version_buf, 22, "%u", file->version);
1376    version_buf[21] = '\0';
1377    file->version_str = strdup(version_buf);
1378    if (version_buf == NULL)
1379    {
1380       edit_free_file(file);
1381       return JB_ERR_MEMORY;
1382    }
1383
1384    *pfile = file;
1385    return JB_ERR_OK;
1386 }
1387
1388
1389 /*********************************************************************
1390  *
1391  * Function    :  edit_read_actions_file
1392  *
1393  * Description :  Read a complete actions file into memory.
1394  *                Handles CGI parameter parsing.  If requested, also
1395  *                checks the file's modification timestamp.
1396  *
1397  *                If this function detects an error in the categories
1398  *                JB_ERR_FILE, JB_ERR_MODIFIED, or JB_ERR_PARSE,
1399  *                then it handles it by filling in the specified
1400  *                response structure and returning JB_ERR_FILE.
1401  *
1402  * Parameters  :
1403  *          1  :  csp = Current client state (buffers, headers, etc...)
1404  *          2  :  rsp = HTTP response.  Only filled in on error.
1405  *          2  :  parameters = map of cgi parameters.
1406  *          3  :  require_version = true to check "ver" parameter.
1407  *          4  :  pfile = Destination for the file.  Will be set
1408  *                        to NULL on error.
1409  *
1410  * CGI Parameters :
1411  *    filename :  The name of the actions file to read, without the
1412  *                path or ".action" extension.
1413  *         ver :  (Only if require_version is nonzero)
1414  *                Timestamp of the actions file.  If wrong, this
1415  *                function fails with JB_ERR_MODIFIED.
1416  *
1417  * Returns     :  JB_ERR_OK     on success
1418  *                JB_ERR_MEMORY on out-of-memory
1419  *                JB_ERR_CGI_PARAMS if "filename" was not specified
1420  *                                  or is not valid.
1421  *                JB_ERR_FILE  if the file does not contain valid data,
1422  *                             or if file cannot be opened or
1423  *                             contains no data, or if version
1424  *                             checking was requested and failed.
1425  *
1426  *********************************************************************/
1427 jb_err edit_read_actions_file(struct client_state *csp,
1428                               struct http_response *rsp,
1429                               const struct map *parameters,
1430                               int require_version,
1431                               struct editable_file **pfile)
1432 {
1433    jb_err err;
1434    struct editable_file *file;
1435
1436    assert(csp);
1437    assert(parameters);
1438    assert(pfile);
1439
1440    *pfile = NULL;
1441
1442    err = edit_read_file(csp, parameters, require_version, ".action", &file);
1443    if (err)
1444    {
1445       /* Try to handle if possible */
1446       if (err == JB_ERR_FILE)
1447       {
1448          err = cgi_error_file(csp, rsp, lookup(parameters, "filename"));
1449       }
1450       else if (err == JB_ERR_MODIFIED)
1451       {
1452          err = cgi_error_modified(csp, rsp, lookup(parameters, "filename"));
1453       }
1454       if (err == JB_ERR_OK)
1455       {
1456          /*
1457           * Signal to higher-level CGI code that there was a problem but we
1458           * handled it, they should just return JB_ERR_OK.
1459           */
1460          err = JB_ERR_FILE;
1461       }
1462       return err;
1463    }
1464
1465    err = edit_parse_actions_file(file);
1466    if (err)
1467    {
1468       if (err == JB_ERR_PARSE)
1469       {
1470          err = cgi_error_parse(csp, rsp, file);
1471          if (err == JB_ERR_OK)
1472          {
1473             /*
1474              * Signal to higher-level CGI code that there was a problem but we
1475              * handled it, they should just return JB_ERR_OK.
1476              */
1477             err = JB_ERR_FILE;
1478          }
1479       }
1480       edit_free_file(file);
1481       return err;
1482    }
1483
1484    *pfile = file;
1485    return JB_ERR_OK;
1486 }
1487
1488
1489 /*********************************************************************
1490  *
1491  * Function    :  get_file_name_param
1492  *
1493  * Description :  Get the name of the file to edit from the parameters
1494  *                passed to a CGI function.  This function handles
1495  *                security checks such as blocking urls containing
1496  *                "/" or ".", prepending the config file directory,
1497  *                and adding the specified suffix.
1498  *
1499  *                (This is an essential security check, otherwise
1500  *                users may be able to pass "../../../etc/passwd"
1501  *                and overwrite the password file [linux], "prn:"
1502  *                and print random data [Windows], etc...)
1503  *
1504  *                This function only allows filenames contining the
1505  *                characters '-', '_', 'A'-'Z', 'a'-'z', and '0'-'9'.
1506  *                That's probably too restrictive but at least it's
1507  *                secure.
1508  *
1509  * Parameters  :
1510  *           1 :  csp = Current client state (buffers, headers, etc...)
1511  *           2 :  parameters = map of cgi parameters
1512  *           3 :  suffix = File extension, e.g. ".actions"
1513  *           4 :  pfilename = destination for full filename.  Caller
1514  *                free()s.  Set to NULL on error.
1515  *           5 :  pparam = destination for partial filename,
1516  *                suitable for use in another URL.  Allocated as part
1517  *                of the map "parameters", so don't free it.
1518  *                Set to NULL if not specified.
1519  *
1520  * Returns     :  JB_ERR_OK         on success
1521  *                JB_ERR_MEMORY     on out-of-memory
1522  *                JB_ERR_CGI_PARAMS if "filename" was not specified
1523  *                                  or is not valid.
1524  *
1525  *********************************************************************/
1526 static jb_err get_file_name_param(struct client_state *csp,
1527                                   const struct map *parameters,
1528                                   const char *param_name,
1529                                   const char *suffix,
1530                                   char **pfilename,
1531                                   const char **pparam)
1532 {
1533    const char *param;
1534    const char *s;
1535    char *name;
1536    char *fullpath;
1537    char ch;
1538    int len;
1539
1540    assert(csp);
1541    assert(parameters);
1542    assert(suffix);
1543    assert(pfilename);
1544    assert(pparam);
1545
1546    *pfilename = NULL;
1547    *pparam = NULL;
1548
1549    param = lookup(parameters, param_name);
1550    if (!*param)
1551    {
1552       return JB_ERR_CGI_PARAMS;
1553    }
1554
1555    *pparam = param;
1556
1557    len = strlen(param);
1558    if (len >= FILENAME_MAX)
1559    {
1560       /* Too long. */
1561       return JB_ERR_CGI_PARAMS;
1562    }
1563
1564    /* Check every character to see if it's legal */
1565    s = param;
1566    while ((ch = *s++) != '\0')
1567    {
1568       if ( ((ch < 'A') || (ch > 'Z'))
1569         && ((ch < 'a') || (ch > 'z'))
1570         && ((ch < '0') || (ch > '9'))
1571         && (ch != '-')
1572         && (ch != '_') )
1573       {
1574          /* Probable hack attempt. */
1575          return JB_ERR_CGI_PARAMS;
1576       }
1577    }
1578
1579    /* Append extension */
1580    name = malloc(len + strlen(suffix) + 1);
1581    if (name == NULL)
1582    {
1583       return JB_ERR_MEMORY;
1584    }
1585    strcpy(name, param);
1586    strcpy(name + len, suffix);
1587
1588    /* Prepend path */
1589    fullpath = make_path(csp->config->confdir, name);
1590    free(name);
1591    if (fullpath == NULL)
1592    {
1593       return JB_ERR_MEMORY;
1594    }
1595
1596    /* Success */
1597    *pfilename = fullpath;
1598
1599    return JB_ERR_OK;
1600 }
1601
1602
1603 /*********************************************************************
1604  *
1605  * Function    :  get_number_param
1606  *
1607  * Description :  Get a non-negative integer from the parameters
1608  *                passed to a CGI function.
1609  *
1610  * Parameters  :
1611  *           1 :  csp = Current client state (buffers, headers, etc...)
1612  *           2 :  parameters = map of cgi parameters
1613  *           3 :  name = Name of CGI parameter to read
1614  *           4 :  pvalue = destination for value.
1615  *                         Set to -1 on error.
1616  *
1617  * Returns     :  JB_ERR_OK         on success
1618  *                JB_ERR_MEMORY     on out-of-memory
1619  *                JB_ERR_CGI_PARAMS if the parameter was not specified
1620  *                                  or is not valid.
1621  *
1622  *********************************************************************/
1623 static jb_err get_number_param(struct client_state *csp,
1624                                const struct map *parameters,
1625                                char *name,
1626                                unsigned *pvalue)
1627 {
1628    const char *param;
1629    char ch;
1630    unsigned value;
1631
1632    assert(csp);
1633    assert(parameters);
1634    assert(name);
1635    assert(pvalue);
1636
1637    *pvalue = -1;
1638
1639    param = lookup(parameters, name);
1640    if (!*param)
1641    {
1642       return JB_ERR_CGI_PARAMS;
1643    }
1644
1645    /* We don't use atoi because I want to check this carefully... */
1646
1647    value = 0;
1648    while ((ch = *param++) != '\0')
1649    {
1650       if ((ch < '0') || (ch > '9'))
1651       {
1652          return JB_ERR_CGI_PARAMS;
1653       }
1654
1655       ch -= '0';
1656
1657       /* Note:
1658        *
1659        * <limits.h> defines UINT_MAX
1660        *
1661        * (UINT_MAX - ch) / 10 is the largest number that 
1662        *     can be safely multiplied by 10 then have ch added.
1663        */
1664       if (value > ((UINT_MAX - (unsigned)ch) / 10U))
1665       {
1666          return JB_ERR_CGI_PARAMS;
1667       }
1668
1669       value = value * 10 + ch;
1670    }
1671
1672    /* Success */
1673    *pvalue = value;
1674
1675    return JB_ERR_OK;
1676 }
1677
1678
1679 /*********************************************************************
1680  *
1681  * Function    :  map_radio
1682  *
1683  * Description :  Map a set of radio button values.  E.g. if you have
1684  *                3 radio buttons, declare them as:
1685  *                  <option type="radio" name="xyz" @xyz-a@>
1686  *                  <option type="radio" name="xyz" @xyz-b@>
1687  *                  <option type="radio" name="xyz" @xyz-c@>
1688  *                Then map one of the @xyz-?@ variables to "checked"
1689  *                and all the others to empty by calling:
1690  *                map_radio(exports, "xyz", "abc", sel)
1691  *                Where 'sel' is 'a', 'b', or 'c'.
1692  *
1693  * Parameters  :
1694  *           1 :  exports = Exports map to modify.
1695  *           2 :  optionname = name for map
1696  *           3 :  values = null-terminated list of values;
1697  *           4 :  value = Selected value.
1698  *
1699  * CGI Parameters : None
1700  *
1701  * Returns     :  JB_ERR_OK     on success
1702  *                JB_ERR_MEMORY on out-of-memory
1703  *
1704  *********************************************************************/
1705 static jb_err map_radio(struct map * exports,
1706                         const char * optionname, 
1707                         const char * values,
1708                         char value)
1709 {
1710    int len;
1711    char * buf;
1712    char * p;
1713    char c;
1714    
1715    assert(exports);
1716    assert(optionname);
1717    assert(values);
1718
1719    len = strlen(optionname);
1720    buf = malloc(len + 3);
1721    if (buf == NULL)
1722    {
1723       return JB_ERR_MEMORY;
1724    }
1725
1726    strcpy(buf, optionname);
1727    p = buf + len;
1728    *p++ = '-';
1729    p[1] = '\0';
1730
1731    while ((c = *values++) != '\0')
1732    {
1733       if (c != value)
1734       {
1735          *p = c;
1736          if (map(exports, buf, 1, "", 1))
1737          {
1738             free(buf);
1739             return JB_ERR_MEMORY;
1740          }
1741       }
1742    }
1743
1744    *p = value;
1745    if (map(exports, buf, 0, "checked", 1))
1746    {
1747       free(buf);
1748       return JB_ERR_MEMORY;
1749    }
1750
1751    return JB_ERR_OK;
1752 }
1753
1754
1755 /*********************************************************************
1756  *
1757  * Function    :  actions_to_radio
1758  *
1759  * Description :  Converts a actionsfile entry into settings for
1760  *                radio buttons and edit boxes on a HTML form.
1761  *
1762  * Parameters  :
1763  *          1  :  exports = List of substitutions to add to.
1764  *          2  :  action  = Action to read
1765  *
1766  * Returns     :  JB_ERR_OK     on success
1767  *                JB_ERR_MEMORY on out-of-memory
1768  *
1769  *********************************************************************/
1770 static jb_err actions_to_radio(struct map * exports,
1771                                const struct action_spec *action)
1772 {
1773    unsigned mask = action->mask;
1774    unsigned add  = action->add;
1775    int mapped_param;
1776    int checked;
1777    char current_mode;
1778
1779    assert(exports);
1780    assert(action);
1781
1782    mask = action->mask;
1783    add  = action->add;
1784
1785    /* sanity - prevents "-feature +feature" */
1786    mask |= add;
1787
1788
1789 #define DEFINE_ACTION_BOOL(name, bit)                 \
1790    if (!(mask & bit))                                 \
1791    {                                                  \
1792       current_mode = 'n';                             \
1793    }                                                  \
1794    else if (add & bit)                                \
1795    {                                                  \
1796       current_mode = 'y';                             \
1797    }                                                  \
1798    else                                               \
1799    {                                                  \
1800       current_mode = 'x';                             \
1801    }                                                  \
1802    if (map_radio(exports, name, "ynx", current_mode)) \
1803    {                                                  \
1804       return JB_ERR_MEMORY;                           \
1805    }
1806
1807 #define DEFINE_ACTION_STRING(name, bit, index)        \
1808    DEFINE_ACTION_BOOL(name, bit);                     \
1809    mapped_param = 0;
1810
1811 #define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default)  \
1812    if (add & bit)                                                    \
1813    {                                                                 \
1814       checked = !strcmp(action->string[index], value);               \
1815    }                                                                 \
1816    else                                                              \
1817    {                                                                 \
1818       checked = is_default;                                          \
1819    }                                                                 \
1820    mapped_param |= checked;                                          \
1821    if (map(exports, name "-param-" value, 1, (checked ? "checked" : ""), 1)) \
1822    {                                                                 \
1823       return JB_ERR_MEMORY;                                          \
1824    }
1825
1826 #define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val)       \
1827    if (map(exports, name "-param-custom", 1,                         \
1828            ((!mapped_param) ? "checked" : ""), 1))                   \
1829    {                                                                 \
1830       return JB_ERR_MEMORY;                                          \
1831    }                                                                 \
1832    if (map(exports, name "-param", 1,                                \
1833            (((add & bit) && !mapped_param) ?                         \
1834            action->string[index] : default_val), 1))                 \
1835    {                                                                 \
1836       return JB_ERR_MEMORY;                                          \
1837    }
1838
1839 #define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val)     \
1840    if (map(exports, name "-param", 1,                                \
1841            ((add & bit) ? action->string[index] : default_val), 1))  \
1842    {                                                                 \
1843       return JB_ERR_MEMORY;                                          \
1844    }
1845
1846 #define DEFINE_ACTION_MULTI(name, index)              \
1847    if (action->multi_add[index]->first)               \
1848    {                                                  \
1849       current_mode = 'y';                             \
1850    }                                                  \
1851    else if (action->multi_remove_all[index])          \
1852    {                                                  \
1853       current_mode = 'n';                             \
1854    }                                                  \
1855    else if (action->multi_remove[index]->first)       \
1856    {                                                  \
1857       current_mode = 'y';                             \
1858    }                                                  \
1859    else                                               \
1860    {                                                  \
1861       current_mode = 'x';                             \
1862    }                                                  \
1863    if (map_radio(exports, name, "ynx", current_mode)) \
1864    {                                                  \
1865       return JB_ERR_MEMORY;                           \
1866    }
1867
1868 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
1869
1870 #include "actionlist.h"
1871
1872 #undef DEFINE_ACTION_MULTI
1873 #undef DEFINE_ACTION_STRING
1874 #undef DEFINE_ACTION_BOOL
1875 #undef DEFINE_ACTION_ALIAS
1876 #undef DEFINE_CGI_PARAM_CUSTOM
1877 #undef DEFINE_CGI_PARAM_RADIO
1878 #undef DEFINE_CGI_PARAM_NO_RADIO
1879
1880    return JB_ERR_OK;
1881 }
1882
1883
1884 /*********************************************************************
1885  *
1886  * Function    :  actions_from_radio
1887  *
1888  * Description :  Converts a map of parameters passed to a CGI function
1889  *                into an actionsfile entry.
1890  *
1891  * Parameters  :
1892  *          1  :  parameters = parameters to the CGI call
1893  *          2  :  action  = Action to change.  Must be valid before
1894  *                          the call, actions not specified will be
1895  *                          left unchanged.
1896  *
1897  * Returns     :  JB_ERR_OK     on success
1898  *                JB_ERR_MEMORY on out-of-memory
1899  *
1900  *********************************************************************/
1901 static jb_err actions_from_radio(const struct map * parameters,
1902                               struct action_spec *action)
1903 {
1904    const char * param;
1905    char * param_dup;
1906    char ch;
1907
1908    assert(parameters);
1909    assert(action);
1910
1911 #define DEFINE_ACTION_BOOL(name, bit)                 \
1912    if (NULL != (param = lookup(parameters, name)))    \
1913    {                                                  \
1914       ch = toupper((int)param[0]);                    \
1915       if (ch == 'Y')                                  \
1916       {                                               \
1917          action->add  |= bit;                         \
1918          action->mask |= bit;                         \
1919       }                                               \
1920       else if (ch == 'N')                             \
1921       {                                               \
1922          action->add  &= ~bit;                        \
1923          action->mask &= ~bit;                        \
1924       }                                               \
1925       else if (ch == 'X')                             \
1926       {                                               \
1927          action->add  &= ~bit;                        \
1928          action->mask |= bit;                         \
1929       }                                               \
1930    }
1931
1932 #define DEFINE_ACTION_STRING(name, bit, index)                    \
1933    if (NULL != (param = lookup(parameters, name)))                \
1934    {                                                              \
1935       ch = toupper((int)param[0]);                                \
1936       if (ch == 'Y')                                              \
1937       {                                                           \
1938          param = lookup(parameters, name "-mode");                \
1939          if ((*param == '\0') || (0 == strcmp(param, "CUSTOM")))  \
1940          {                                                        \
1941             param = lookup(parameters, name "-param");            \
1942          }                                                        \
1943          if (*param != '\0')                                      \
1944          {                                                        \
1945             if (NULL == (param_dup = strdup(param)))              \
1946             {                                                     \
1947                return JB_ERR_MEMORY;                              \
1948             }                                                     \
1949             freez(action->string[index]);                         \
1950             action->add  |= bit;                                  \
1951             action->mask |= bit;                                  \
1952             action->string[index] = param_dup;                    \
1953          }                                                        \
1954       }                                                           \
1955       else if (ch == 'N')                                         \
1956       {                                                           \
1957          if (action->add & bit)                                   \
1958          {                                                        \
1959             freez(action->string[index]);                         \
1960          }                                                        \
1961          action->add  &= ~bit;                                    \
1962          action->mask &= ~bit;                                    \
1963       }                                                           \
1964       else if (ch == 'X')                                         \
1965       {                                                           \
1966          if (action->add & bit)                                   \
1967          {                                                        \
1968             freez(action->string[index]);                         \
1969          }                                                        \
1970          action->add  &= ~bit;                                    \
1971          action->mask |= bit;                                     \
1972       }                                                           \
1973    }
1974
1975 #define DEFINE_ACTION_MULTI(name, index)                          \
1976    if (NULL != (param = lookup(parameters, name)))                \
1977    {                                                              \
1978       ch = toupper((int)param[0]);                                \
1979       if (ch == 'Y')                                              \
1980       {                                                           \
1981          /* FIXME */                                              \
1982       }                                                           \
1983       else if (ch == 'N')                                         \
1984       {                                                           \
1985          list_remove_all(action->multi_add[index]);               \
1986          list_remove_all(action->multi_remove[index]);            \
1987          action->multi_remove_all[index] = 1;                     \
1988       }                                                           \
1989       else if (ch == 'X')                                         \
1990       {                                                           \
1991          list_remove_all(action->multi_add[index]);               \
1992          list_remove_all(action->multi_remove[index]);            \
1993          action->multi_remove_all[index] = 0;                     \
1994       }                                                           \
1995    }
1996
1997 #define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val)
1998 #define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default)
1999 #define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val)
2000
2001 #define DEFINE_ACTION_ALIAS 0 /* No aliases for URL parsing */
2002
2003 #include "actionlist.h"
2004
2005 #undef DEFINE_ACTION_MULTI
2006 #undef DEFINE_ACTION_STRING
2007 #undef DEFINE_ACTION_BOOL
2008 #undef DEFINE_ACTION_ALIAS
2009 #undef DEFINE_CGI_PARAM_CUSTOM
2010 #undef DEFINE_CGI_PARAM_RADIO
2011 #undef DEFINE_CGI_PARAM_NO_RADIO
2012
2013    return JB_ERR_OK;
2014 }
2015
2016
2017 /*********************************************************************
2018  *
2019  * Function    :  cgi_error_modified
2020  *
2021  * Description :  CGI function that is called when a file is modified
2022  *                outside the CGI editor.
2023  *               
2024  * Parameters  :
2025  *           1 :  csp = Current client state (buffers, headers, etc...)
2026  *           2 :  rsp = http_response data structure for output
2027  *           3 :  filename = The file that was modified.
2028  *
2029  * CGI Parameters : none
2030  *
2031  * Returns     :  JB_ERR_OK on success
2032  *                JB_ERR_MEMORY on out-of-memory error.  
2033  *
2034  *********************************************************************/
2035 jb_err cgi_error_modified(struct client_state *csp,
2036                           struct http_response *rsp,
2037                           const char *filename)
2038 {
2039    struct map *exports;
2040    jb_err err;
2041
2042    assert(csp);
2043    assert(rsp);
2044    assert(filename);
2045
2046    if (NULL == (exports = default_exports(csp, NULL)))
2047    {
2048       return JB_ERR_MEMORY;
2049    }
2050
2051    err = map(exports, "filename", 1, filename, 1);
2052    if (err)
2053    {
2054       free_map(exports);
2055       return err;
2056    }
2057
2058    return template_fill_for_cgi(csp, "cgi-error-modified", exports, rsp);
2059 }
2060
2061
2062 /*********************************************************************
2063  *
2064  * Function    :  cgi_error_parse
2065  *
2066  * Description :  CGI function that is called when a file cannot
2067  *                be parsed by the CGI editor.
2068  *               
2069  * Parameters  :
2070  *           1 :  csp = Current client state (buffers, headers, etc...)
2071  *           2 :  rsp = http_response data structure for output
2072  *           3 :  file = The file that was modified.
2073  *
2074  * CGI Parameters : none
2075  *
2076  * Returns     :  JB_ERR_OK on success
2077  *                JB_ERR_MEMORY on out-of-memory error.  
2078  *
2079  *********************************************************************/
2080 jb_err cgi_error_parse(struct client_state *csp,
2081                        struct http_response *rsp,
2082                        struct editable_file *file)
2083 {
2084    struct map *exports;
2085    jb_err err;
2086    struct file_line *cur_line;
2087
2088    assert(csp);
2089    assert(rsp);
2090    assert(file);
2091
2092    if (NULL == (exports = default_exports(csp, NULL)))
2093    {
2094       return JB_ERR_MEMORY;
2095    }
2096
2097    err = map(exports, "filename", 1, file->identifier, 1);
2098    err = err || map(exports, "parse-error", 1, file->parse_error_text, 1);
2099
2100    cur_line = file->parse_error;
2101    assert(cur_line);
2102
2103    err = err || map(exports, "line-raw", 1, html_encode(cur_line->raw), 0);
2104    err = err || map(exports, "line-data", 1, html_encode(cur_line->unprocessed), 0);
2105
2106    if (err)
2107    {
2108       free_map(exports);
2109       return err;
2110    }
2111
2112    return template_fill_for_cgi(csp, "cgi-error-parse", exports, rsp);
2113 }
2114
2115
2116 /*********************************************************************
2117  *
2118  * Function    :  cgi_error_file
2119  *
2120  * Description :  CGI function that is called when a file cannot be
2121  *                opened by the CGI editor.
2122  *               
2123  * Parameters  :
2124  *           1 :  csp = Current client state (buffers, headers, etc...)
2125  *           2 :  rsp = http_response data structure for output
2126  *           3 :  filename = The file that was modified.
2127  *
2128  * CGI Parameters : none
2129  *
2130  * Returns     :  JB_ERR_OK on success
2131  *                JB_ERR_MEMORY on out-of-memory error.  
2132  *
2133  *********************************************************************/
2134 jb_err cgi_error_file(struct client_state *csp,
2135                       struct http_response *rsp,
2136                       const char *filename)
2137 {
2138    struct map *exports;
2139    jb_err err;
2140
2141    assert(csp);
2142    assert(rsp);
2143    assert(filename);
2144
2145    if (NULL == (exports = default_exports(csp, NULL)))
2146    {
2147       return JB_ERR_MEMORY;
2148    }
2149
2150    err = map(exports, "filename", 1, filename, 1);
2151    if (err)
2152    {
2153       free_map(exports);
2154       return err;
2155    }
2156
2157    return template_fill_for_cgi(csp, "cgi-error-file", exports, rsp);
2158 }
2159
2160
2161 /*********************************************************************
2162  *
2163  * Function    :  cgi_error_bad_param
2164  *
2165  * Description :  CGI function that is called if the parameters
2166  *                (query string) for a CGI were wrong.
2167  *               
2168  * Parameters  :
2169  *           1 :  csp = Current client state (buffers, headers, etc...)
2170  *           2 :  rsp = http_response data structure for output
2171  *
2172  * CGI Parameters : none
2173  *
2174  * Returns     :  JB_ERR_OK on success
2175  *                JB_ERR_MEMORY on out-of-memory error.  
2176  *
2177  *********************************************************************/
2178 jb_err cgi_error_disabled(struct client_state *csp,
2179                           struct http_response *rsp)
2180 {
2181    struct map *exports;
2182
2183    assert(csp);
2184    assert(rsp);
2185
2186    if (NULL == (exports = default_exports(csp, NULL)))
2187    {
2188       return JB_ERR_MEMORY;
2189    }
2190
2191    return template_fill_for_cgi(csp, "cgi-error-disabled", exports, rsp);
2192 }
2193
2194
2195 /*********************************************************************
2196  *
2197  * Function    :  cgi_edit_actions
2198  *
2199  * Description :  CGI function that allows the user to choose which
2200  *                actions file to edit.
2201  *
2202  * Parameters  :
2203  *           1 :  csp = Current client state (buffers, headers, etc...)
2204  *           2 :  rsp = http_response data structure for output
2205  *           3 :  parameters = map of cgi parameters
2206  *
2207  * CGI Parameters : None
2208  *
2209  * Returns     :  JB_ERR_OK on success
2210  *                JB_ERR_MEMORY on out-of-memory error
2211  *
2212  *********************************************************************/
2213 jb_err cgi_edit_actions(struct client_state *csp,
2214                         struct http_response *rsp,
2215                         const struct map *parameters)
2216 {
2217
2218    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2219    {
2220       return cgi_error_disabled(csp, rsp);
2221    }
2222
2223    /* FIXME: Incomplete */
2224    rsp->status = strdup("302 Local Redirect from Junkbuster");
2225    if (rsp->status == NULL)
2226    {
2227       return JB_ERR_MEMORY;
2228    }
2229    if (enlist_unique_header(rsp->headers, "Location", "http://ijbswa.sourceforge.net/config/edit-actions-list?filename=edit"))
2230    {
2231       free(rsp->status);
2232       rsp->status = NULL;
2233       return JB_ERR_MEMORY;
2234    }
2235
2236    return JB_ERR_OK;
2237 }
2238
2239
2240 /*********************************************************************
2241  *
2242  * Function    :  cgi_edit_actions_list
2243  *
2244  * Description :  CGI function that edits the actions list.
2245  *                FIXME: This function shouldn't FATAL ever.
2246  *                FIXME: This function doesn't check the retval of map()
2247  * Parameters  :
2248  *           1 :  csp = Current client state (buffers, headers, etc...)
2249  *           2 :  rsp = http_response data structure for output
2250  *           3 :  parameters = map of cgi parameters
2251  *
2252  * CGI Parameters : filename
2253  *
2254  * Returns     :  JB_ERR_OK     on success
2255  *                JB_ERR_MEMORY on out-of-memory
2256  *                JB_ERR_FILE   if the file cannot be opened or
2257  *                              contains no data
2258  *                JB_ERR_CGI_PARAMS if "filename" was not specified
2259  *                                  or is not valid.
2260  *
2261  *********************************************************************/
2262 jb_err cgi_edit_actions_list(struct client_state *csp,
2263                              struct http_response *rsp,
2264                              const struct map *parameters)
2265 {
2266    char * section_template;
2267    char * url_template;
2268    char * sections;
2269    char * urls;
2270    char buf[50];
2271    char * s;
2272    struct map * exports;
2273    struct map * section_exports;
2274    struct map * url_exports;
2275    struct editable_file * file;
2276    struct file_line * cur_line;
2277    int line_number = 0;
2278    int url_1_2;
2279    jb_err err;
2280
2281    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2282    {
2283       return cgi_error_disabled(csp, rsp);
2284    }
2285
2286    err = edit_read_actions_file(csp, rsp, parameters, 0, &file);
2287    if (err)
2288    {
2289       /* No filename specified, can't read file, or out of memory. */
2290       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2291    }
2292
2293    if (NULL == (exports = default_exports(csp, NULL)))
2294    {
2295       edit_free_file(file);
2296       return JB_ERR_MEMORY;
2297    }
2298
2299    err = map(exports, "filename", 1, file->identifier, 1);
2300    err = err || map(exports, "ver", 1, file->version_str, 1);
2301    if (err)
2302    {
2303       edit_free_file(file);
2304       free_map(exports);
2305       return err;
2306    }
2307
2308    /* Should do all global exports above this point */
2309
2310    err = template_load(csp, &section_template, "edit-actions-list-section");
2311    if (err)
2312    {
2313       edit_free_file(file);
2314       free_map(exports);
2315       if (err == JB_ERR_FILE)
2316       {
2317          return cgi_error_no_template(csp, rsp, "edit-actions-list-section");
2318       }
2319       return err;
2320    }
2321    
2322    err = template_load(csp, &url_template, "edit-actions-list-url");
2323    if (err)
2324    {
2325       free(section_template);
2326       edit_free_file(file);
2327       free_map(exports);
2328       if (err == JB_ERR_FILE)
2329       {
2330          return cgi_error_no_template(csp, rsp, "edit-actions-list-url");
2331       }
2332       return err;
2333    }
2334
2335    err = template_fill(&section_template, exports);
2336    if (err)
2337    {
2338       free(url_template);
2339       edit_free_file(file);
2340       free_map(exports);
2341       free(url_template);
2342       return err;
2343    }
2344
2345    err = template_fill(&url_template, exports);
2346    if (err)
2347    {
2348       free(section_template);
2349       edit_free_file(file);
2350       free_map(exports);
2351       return err;
2352    }
2353
2354    /* Find start of actions in file */
2355    cur_line = file->lines;
2356    line_number = 1;
2357    while ((cur_line != NULL) && (cur_line->type != FILE_LINE_ACTION))
2358    {
2359       cur_line = cur_line->next;
2360       line_number++;
2361    }
2362
2363    if (NULL == (sections = strdup("")))
2364    {
2365       free(section_template);
2366       free(url_template);
2367       edit_free_file(file);
2368       free_map(exports);
2369       return JB_ERR_MEMORY;
2370    }
2371
2372    while ((cur_line != NULL) && (cur_line->type == FILE_LINE_ACTION))
2373    {
2374       if (NULL == (section_exports = new_map()))
2375       {
2376          free(sections);
2377          free(section_template);
2378          free(url_template);
2379          edit_free_file(file);
2380          free_map(exports);
2381          return JB_ERR_MEMORY;
2382       }
2383
2384       snprintf(buf, 50, "%d", line_number);
2385       err = map(section_exports, "sectionid", 1, buf, 1);
2386
2387       err = err || map(section_exports, "actions", 1, 
2388                        actions_to_html(cur_line->data.action), 0);
2389
2390       if ((cur_line->next != NULL) && (cur_line->next->type == FILE_LINE_URL))
2391       {
2392          /* This section contains at least one URL, don't allow delete */
2393          err = err || map_block_killer(section_exports, "empty-section");
2394       }
2395
2396       if (err)
2397       {
2398          free(sections);
2399          free(section_template);
2400          free(url_template);
2401          edit_free_file(file);
2402          free_map(exports);
2403          free_map(section_exports);
2404          return err;
2405       }
2406
2407       /* Should do all section-specific exports above this point */
2408
2409       if (NULL == (urls = strdup("")))
2410       {
2411          free(sections);
2412          free(section_template);
2413          free(url_template);
2414          edit_free_file(file);
2415          free_map(exports);
2416          free_map(section_exports);
2417          return JB_ERR_MEMORY;
2418       }
2419
2420       url_1_2 = 2;
2421
2422       cur_line = cur_line->next;
2423       line_number++;
2424
2425       while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL))
2426       {
2427          if (NULL == (url_exports = new_map()))
2428          {
2429             free(urls);
2430             free(sections);
2431             free(section_template);
2432             free(url_template);
2433             edit_free_file(file);
2434             free_map(exports);
2435             free_map(section_exports);
2436             return JB_ERR_MEMORY;
2437          }
2438
2439          snprintf(buf, 50, "%d", line_number);
2440          err = map(url_exports, "urlid", 1, buf, 1);
2441
2442          snprintf(buf, 50, "%d", url_1_2);
2443          err = err || map(url_exports, "url-1-2", 1, buf, 1);
2444
2445          err = err || map(url_exports, "url", 1, 
2446                           html_encode(cur_line->unprocessed), 0);
2447
2448          if (err)
2449          {
2450             free(urls);
2451             free(sections);
2452             free(section_template);
2453             free(url_template);
2454             edit_free_file(file);
2455             free_map(exports);
2456             free_map(section_exports);
2457             free_map(url_exports);
2458             return err;
2459          }
2460
2461          if (NULL == (s = strdup(url_template)))
2462          {
2463             free(urls);
2464             free(sections);
2465             free(section_template);
2466             free(url_template);
2467             edit_free_file(file);
2468             free_map(exports);
2469             free_map(section_exports);
2470             free_map(url_exports);
2471             return JB_ERR_MEMORY;
2472          }
2473
2474          err =        template_fill(&s, section_exports);
2475          err = err || template_fill(&s, url_exports);
2476          err = err || string_append(&urls, s);
2477          free_map(url_exports);
2478          freez(s);
2479
2480          if (err)
2481          {
2482             freez(urls);
2483             free(sections);
2484             free(section_template);
2485             free(url_template);
2486             edit_free_file(file);
2487             free_map(exports);
2488             free_map(section_exports);
2489             return err;
2490          }
2491
2492          url_1_2 = 3 - url_1_2;
2493
2494          cur_line = cur_line->next;
2495          line_number++;
2496       }
2497
2498       err = map(section_exports, "urls", 1, urls, 0);
2499
2500       if (err)
2501       {
2502          free(sections);
2503          free(section_template);
2504          free(url_template);
2505          edit_free_file(file);
2506          free_map(exports);
2507          free_map(section_exports);
2508          return err;
2509       }
2510
2511       /* Could also do section-specific exports here, but it wouldn't be as fast */
2512
2513       if (NULL == (s = strdup(section_template)))
2514       {
2515          free(sections);
2516          free(section_template);
2517          free(url_template);
2518          edit_free_file(file);
2519          free_map(exports);
2520          free_map(section_exports);
2521          return JB_ERR_MEMORY;
2522       }
2523
2524       err = template_fill(&s, section_exports);
2525       err = err || string_append(&sections, s);
2526       freez(s);
2527       free_map(section_exports);
2528
2529       if (err)
2530       {
2531          freez(sections);
2532          free(section_template);
2533          free(url_template);
2534          edit_free_file(file);
2535          free_map(exports);
2536          return err;
2537       }
2538    }
2539
2540    edit_free_file(file);
2541    free(section_template);
2542    free(url_template);
2543
2544    err = map(exports, "sections", 1, sections, 0);
2545    if (err)
2546    {
2547       free_map(exports);
2548       return err;
2549    }
2550
2551    /* Could also do global exports here, but it wouldn't be as fast */
2552
2553    return template_fill_for_cgi(csp, "edit-actions-list", exports, rsp);
2554 }
2555
2556
2557 /*********************************************************************
2558  *
2559  * Function    :  cgi_edit_actions
2560  *
2561  * Description :  CGI function that edits the Actions list.
2562  *
2563  * Parameters  :
2564  *           1 :  csp = Current client state (buffers, headers, etc...)
2565  *           2 :  rsp = http_response data structure for output
2566  *           3 :  parameters = map of cgi parameters
2567  *
2568  * CGI Parameters : None
2569  *
2570  * Returns     :  JB_ERR_OK     on success
2571  *                JB_ERR_MEMORY on out-of-memory
2572  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
2573  *                                  specified or not valid.
2574  *
2575  *********************************************************************/
2576 jb_err cgi_edit_actions_for_url(struct client_state *csp,
2577                                 struct http_response *rsp,
2578                                 const struct map *parameters)
2579 {
2580    struct map * exports;
2581    unsigned sectionid;
2582    struct editable_file * file;
2583    struct file_line * cur_line;
2584    unsigned line_number;
2585    jb_err err;
2586
2587    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2588    {
2589       return cgi_error_disabled(csp, rsp);
2590    }
2591
2592    err = get_number_param(csp, parameters, "section", &sectionid);
2593    if (err)
2594    {
2595       return err;
2596    }
2597
2598    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2599    if (err)
2600    {
2601       /* No filename specified, can't read file, modified, or out of memory. */
2602       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2603    }
2604
2605    cur_line = file->lines;
2606
2607    for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
2608    {
2609       cur_line = cur_line->next;
2610    }
2611
2612    if ( (cur_line == NULL)
2613      || (line_number != sectionid)
2614      || (sectionid < 1)
2615      || (cur_line->type != FILE_LINE_ACTION))
2616    {
2617       /* Invalid "sectionid" parameter */
2618       edit_free_file(file);
2619       return JB_ERR_CGI_PARAMS;
2620    }
2621
2622    if (NULL == (exports = default_exports(csp, NULL)))
2623    {
2624       edit_free_file(file);
2625       return JB_ERR_MEMORY;
2626    }
2627
2628    err = map(exports, "filename", 1, file->identifier, 1);
2629    err = err || map(exports, "ver", 1, file->version_str, 1);
2630    err = err || map(exports, "section", 1, lookup(parameters, "section"), 1);
2631
2632    err = err || actions_to_radio(exports, cur_line->data.action);
2633
2634    edit_free_file(file);
2635
2636    if (err)
2637    {
2638       free_map(exports);
2639       return err;
2640    }
2641
2642    return template_fill_for_cgi(csp, "edit-actions-for-url", exports, rsp);
2643 }
2644
2645
2646 /*********************************************************************
2647  *
2648  * Function    :  cgi_edit_actions_submit
2649  *
2650  * Description :  CGI function that actually edits the Actions list.
2651  *
2652  * Parameters  :
2653  *           1 :  csp = Current client state (buffers, headers, etc...)
2654  *           2 :  rsp = http_response data structure for output
2655  *           3 :  parameters = map of cgi parameters
2656  *
2657  * CGI Parameters : None
2658  *
2659  * Returns     :  JB_ERR_OK     on success
2660  *                JB_ERR_MEMORY on out-of-memory
2661  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
2662  *                                  specified or not valid.
2663  *
2664  *********************************************************************/
2665 jb_err cgi_edit_actions_submit(struct client_state *csp,
2666                                struct http_response *rsp,
2667                                const struct map *parameters)
2668 {
2669    int sectionid;
2670    char * actiontext;
2671    char * newtext;
2672    int len;
2673    struct editable_file * file;
2674    struct file_line * cur_line;
2675    int line_number;
2676    char * target;
2677    jb_err err;
2678
2679    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2680    {
2681       return cgi_error_disabled(csp, rsp);
2682    }
2683
2684    err = get_number_param(csp, parameters, "section", &sectionid);
2685    if (err)
2686    {
2687       return err;
2688    }
2689
2690    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2691    if (err)
2692    {
2693       /* No filename specified, can't read file, modified, or out of memory. */
2694       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2695    }
2696
2697    cur_line = file->lines;
2698
2699    for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
2700    {
2701       cur_line = cur_line->next;
2702    }
2703
2704    if ( (cur_line == NULL)
2705      || (line_number != sectionid)
2706      || (sectionid < 1)
2707      || (cur_line->type != FILE_LINE_ACTION))
2708    {
2709       /* Invalid "sectionid" parameter */
2710       edit_free_file(file);
2711       return JB_ERR_CGI_PARAMS;
2712    }
2713
2714    err = actions_from_radio(parameters, cur_line->data.action);
2715    if(err)
2716    {
2717       /* Out of memory */
2718       edit_free_file(file);
2719       return err;
2720    }
2721
2722    if (NULL == (actiontext = actions_to_text(cur_line->data.action)))
2723    {
2724       /* Out of memory */
2725       edit_free_file(file);
2726       return JB_ERR_MEMORY;
2727    }
2728
2729    len = strlen(actiontext);
2730    if (len == 0)
2731    {
2732       /*
2733        * Empty action - must special-case this.
2734        * Simply setting len to 1 is sufficient...
2735        */
2736       len = 1;
2737    }
2738
2739    if (NULL == (newtext = malloc(len + 2)))
2740    {
2741       /* Out of memory */
2742       free(actiontext);
2743       edit_free_file(file);
2744       return JB_ERR_MEMORY;
2745    }
2746    strcpy(newtext, actiontext);
2747    free(actiontext);
2748    newtext[0]       = '{';
2749    newtext[len]     = '}';
2750    newtext[len + 1] = '\0';
2751
2752    freez(cur_line->raw);
2753    freez(cur_line->unprocessed);
2754    cur_line->unprocessed = newtext;
2755
2756    err = edit_write_file(file);
2757    if (err)
2758    {
2759       /* Error writing file */
2760       edit_free_file(file);
2761       return err;
2762    }
2763
2764    target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
2765    string_append(&target, file->identifier);
2766
2767    edit_free_file(file);
2768
2769    if (target == NULL)
2770    {
2771       /* Out of memory */
2772       return JB_ERR_MEMORY;
2773    }
2774
2775    rsp->status = strdup("302 Local Redirect from Junkbuster");
2776    if (rsp->status == NULL)
2777    {
2778       free(target);
2779       return JB_ERR_MEMORY;
2780    }
2781    err = enlist_unique_header(rsp->headers, "Location", target);
2782    free(target);
2783
2784    return err;
2785 }
2786
2787
2788 /*********************************************************************
2789  *
2790  * Function    :  cgi_edit_actions_url
2791  *
2792  * Description :  CGI function that actually edits a URL pattern in
2793  *                an actions file.
2794  *
2795  * Parameters  :
2796  *           1 :  csp = Current client state (buffers, headers, etc...)
2797  *           2 :  rsp = http_response data structure for output
2798  *           3 :  parameters = map of cgi parameters
2799  *
2800  * CGI Parameters :
2801  *    filename : Identifies the file to edit
2802  *         ver : File's last-modified time
2803  *     section : Line number of section to edit
2804  *     pattern : Line number of pattern to edit
2805  *      newval : New value for pattern
2806  *
2807  * Returns     :  JB_ERR_OK     on success
2808  *                JB_ERR_MEMORY on out-of-memory
2809  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
2810  *                                  specified or not valid.
2811  *
2812  *********************************************************************/
2813 jb_err cgi_edit_actions_url(struct client_state *csp,
2814                             struct http_response *rsp,
2815                             const struct map *parameters)
2816 {
2817    unsigned sectionid;
2818    unsigned patternid;
2819    const char * newval;
2820    char * new_pattern;
2821    struct editable_file * file;
2822    struct file_line * cur_line;
2823    unsigned line_number;
2824    char * target;
2825    jb_err err;
2826
2827    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2828    {
2829       return cgi_error_disabled(csp, rsp);
2830    }
2831
2832    err = get_number_param(csp, parameters, "section", &sectionid);
2833    err = err || get_number_param(csp, parameters, "pattern", &patternid);
2834    if (err)
2835    {
2836       return err;
2837    }
2838
2839    newval = lookup(parameters, "newval");
2840
2841    if ((*newval == '\0') || (sectionid < 1U) || (patternid < 1U))
2842    {
2843       return JB_ERR_CGI_PARAMS;
2844    }
2845
2846    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2847    if (err)
2848    {
2849       /* No filename specified, can't read file, modified, or out of memory. */
2850       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2851    }
2852
2853    line_number = 1;
2854    cur_line = file->lines;
2855
2856    while ((cur_line != NULL) && (line_number < sectionid))
2857    {
2858       cur_line = cur_line->next;
2859       line_number++;
2860    }
2861
2862    if ( (cur_line == NULL)
2863      || (cur_line->type != FILE_LINE_ACTION))
2864    {
2865       /* Invalid "sectionid" parameter */
2866       edit_free_file(file);
2867       return JB_ERR_CGI_PARAMS;
2868    }
2869
2870    while (line_number < patternid)
2871    {
2872       cur_line = cur_line->next;
2873       line_number++;
2874
2875       if ( (cur_line == NULL)
2876         || ( (cur_line->type != FILE_LINE_URL)
2877           && (cur_line->type != FILE_LINE_BLANK) ) )
2878       {
2879          /* Invalid "patternid" parameter */
2880          edit_free_file(file);
2881          return JB_ERR_CGI_PARAMS;
2882       }
2883    }
2884
2885    if (cur_line->type != FILE_LINE_URL)
2886    {
2887       /* Invalid "patternid" parameter */
2888       edit_free_file(file);
2889       return JB_ERR_CGI_PARAMS;
2890    }
2891
2892    /* At this point, the line to edit is in cur_line */
2893
2894    new_pattern = strdup(newval);
2895    if (NULL == new_pattern)
2896    {
2897       edit_free_file(file);
2898       return JB_ERR_MEMORY;
2899    }
2900
2901    freez(cur_line->raw);
2902    freez(cur_line->unprocessed);
2903    cur_line->unprocessed = new_pattern;
2904
2905    err = edit_write_file(file);
2906    if (err)
2907    {
2908       /* Error writing file */
2909       edit_free_file(file);
2910       return err;
2911    }
2912
2913    target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
2914    string_append(&target, file->identifier);
2915
2916    edit_free_file(file);
2917
2918    if (target == NULL)
2919    {
2920       /* Out of memory */
2921       return JB_ERR_MEMORY;
2922    }
2923
2924    rsp->status = strdup("302 Local Redirect from Junkbuster");
2925    if (rsp->status == NULL)
2926    {
2927       free(target);
2928       return JB_ERR_MEMORY;
2929    }
2930    err = enlist_unique_header(rsp->headers, "Location", target);
2931    free(target);
2932
2933    return err;
2934 }
2935
2936
2937 /*********************************************************************
2938  *
2939  * Function    :  cgi_edit_actions_add_url
2940  *
2941  * Description :  CGI function that actually adds a URL pattern to
2942  *                an actions file.
2943  *
2944  * Parameters  :
2945  *           1 :  csp = Current client state (buffers, headers, etc...)
2946  *           2 :  rsp = http_response data structure for output
2947  *           3 :  parameters = map of cgi parameters
2948  *
2949  * CGI Parameters :
2950  *    filename : Identifies the file to edit
2951  *         ver : File's last-modified time
2952  *     section : Line number of section to edit
2953  *      newval : New pattern
2954  *
2955  * Returns     :  JB_ERR_OK     on success
2956  *                JB_ERR_MEMORY on out-of-memory
2957  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
2958  *                                  specified or not valid.
2959  *
2960  *********************************************************************/
2961 jb_err cgi_edit_actions_add_url(struct client_state *csp,
2962                                 struct http_response *rsp,
2963                                 const struct map *parameters)
2964 {
2965    unsigned sectionid;
2966    unsigned patternid;
2967    const char * newval;
2968    char * new_pattern;
2969    struct file_line * new_line;
2970    struct editable_file * file;
2971    struct file_line * cur_line;
2972    unsigned line_number;
2973    char * target;
2974    jb_err err;
2975
2976    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2977    {
2978       return cgi_error_disabled(csp, rsp);
2979    }
2980
2981    err = get_number_param(csp, parameters, "section", &sectionid);
2982    if (err)
2983    {
2984       return err;
2985    }
2986
2987    newval = lookup(parameters, "newval");
2988
2989    if ((*newval == '\0') || (sectionid < 1U) || (patternid < 1U))
2990    {
2991       return JB_ERR_CGI_PARAMS;
2992    }
2993
2994    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2995    if (err)
2996    {
2997       /* No filename specified, can't read file, modified, or out of memory. */
2998       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2999    }
3000
3001    line_number = 1;
3002    cur_line = file->lines;
3003
3004    while ((cur_line != NULL) && (line_number < sectionid))
3005    {
3006       cur_line = cur_line->next;
3007       line_number++;
3008    }
3009
3010    if ( (cur_line == NULL)
3011      || (cur_line->type != FILE_LINE_ACTION))
3012    {
3013       /* Invalid "sectionid" parameter */
3014       edit_free_file(file);
3015       return JB_ERR_CGI_PARAMS;
3016    }
3017
3018    /* At this point, the section header is in cur_line - add after this. */
3019
3020    new_pattern = strdup(newval);
3021    if (NULL == new_pattern)
3022    {
3023       edit_free_file(file);
3024       return JB_ERR_MEMORY;
3025    }
3026
3027    /* Allocate the new line */
3028    new_line = (struct file_line *)zalloc(sizeof(*new_line));
3029    if (new_line == NULL)
3030    {
3031       free(new_pattern);
3032       edit_free_file(file);
3033       return JB_ERR_MEMORY;
3034    }
3035
3036    /* Fill in the data members of the new line */
3037    new_line->raw = NULL;
3038    new_line->prefix = NULL;
3039    new_line->unprocessed = new_pattern;
3040    new_line->type = FILE_LINE_URL;
3041
3042    /* Link new_line into the list, after cur_line */
3043    new_line->next = cur_line->next;
3044    cur_line->next = new_line;
3045
3046    /* Done making changes, now commit */
3047
3048    err = edit_write_file(file);
3049    if (err)
3050    {
3051       /* Error writing file */
3052       edit_free_file(file);
3053       return err;
3054    }
3055
3056    target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
3057    string_append(&target, file->identifier);
3058
3059    edit_free_file(file);
3060
3061    if (target == NULL)
3062    {
3063       /* Out of memory */
3064       return JB_ERR_MEMORY;
3065    }
3066
3067    rsp->status = strdup("302 Local Redirect from Junkbuster");
3068    if (rsp->status == NULL)
3069    {
3070       free(target);
3071       return JB_ERR_MEMORY;
3072    }
3073    err = enlist_unique_header(rsp->headers, "Location", target);
3074    free(target);
3075
3076    return err;
3077 }
3078
3079
3080 /*********************************************************************
3081  *
3082  * Function    :  cgi_edit_actions_remove_url
3083  *
3084  * Description :  CGI function that actually removes a URL pattern from
3085  *                the actions file.
3086  *
3087  * Parameters  :
3088  *           1 :  csp = Current client state (buffers, headers, etc...)
3089  *           2 :  rsp = http_response data structure for output
3090  *           3 :  parameters = map of cgi parameters
3091  *
3092  * CGI Parameters :
3093  *    filename : Identifies the file to edit
3094  *         ver : File's last-modified time
3095  *     section : Line number of section to edit
3096  *     pattern : Line number of pattern to edit
3097  *
3098  * Returns     :  JB_ERR_OK     on success
3099  *                JB_ERR_MEMORY on out-of-memory
3100  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3101  *                                  specified or not valid.
3102  *
3103  *********************************************************************/
3104 jb_err cgi_edit_actions_remove_url(struct client_state *csp,
3105                                    struct http_response *rsp,
3106                                    const struct map *parameters)
3107 {
3108    unsigned sectionid;
3109    unsigned patternid;
3110    struct editable_file * file;
3111    struct file_line * cur_line;
3112    struct file_line * prev_line;
3113    unsigned line_number;
3114    char * target;
3115    jb_err err;
3116
3117    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3118    {
3119       return cgi_error_disabled(csp, rsp);
3120    }
3121
3122    err = get_number_param(csp, parameters, "section", &sectionid);
3123    err = err || get_number_param(csp, parameters, "pattern", &patternid);
3124    if (err)
3125    {
3126       return err;
3127    }
3128
3129
3130    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3131    if (err)
3132    {
3133       /* No filename specified, can't read file, modified, or out of memory. */
3134       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3135    }
3136
3137    line_number = 1;
3138    cur_line = file->lines;
3139
3140    while ((cur_line != NULL) && (line_number < sectionid))
3141    {
3142       cur_line = cur_line->next;
3143       line_number++;
3144    }
3145
3146    if ( (cur_line == NULL)
3147      || (cur_line->type != FILE_LINE_ACTION))
3148    {
3149       /* Invalid "sectionid" parameter */
3150       edit_free_file(file);
3151       return JB_ERR_CGI_PARAMS;
3152    }
3153
3154    prev_line = NULL;
3155    while (line_number < patternid)
3156    {
3157       prev_line = cur_line;
3158       cur_line = cur_line->next;
3159       line_number++;
3160
3161       if ( (cur_line == NULL)
3162         || ( (cur_line->type != FILE_LINE_URL)
3163           && (cur_line->type != FILE_LINE_BLANK) ) )
3164       {
3165          /* Invalid "patternid" parameter */
3166          edit_free_file(file);
3167          return JB_ERR_CGI_PARAMS;
3168       }
3169    }
3170
3171    if (cur_line->type != FILE_LINE_URL)
3172    {
3173       /* Invalid "patternid" parameter */
3174       edit_free_file(file);
3175       return JB_ERR_CGI_PARAMS;
3176    }
3177
3178    assert(prev_line);
3179
3180    /* At this point, the line to remove is in cur_line, and the previous
3181     * one is in prev_line
3182     */
3183
3184    /* Unlink cur_line */
3185    prev_line->next = cur_line->next;
3186    cur_line->next = NULL;
3187
3188    /* Free cur_line */
3189    edit_free_file_lines(cur_line);
3190
3191    err = edit_write_file(file);
3192    if (err)
3193    {
3194       /* Error writing file */
3195       edit_free_file(file);
3196       return err;
3197    }
3198
3199    target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
3200    string_append(&target, file->identifier);
3201
3202    edit_free_file(file);
3203
3204    if (target == NULL)
3205    {
3206       /* Out of memory */
3207       return JB_ERR_MEMORY;
3208    }
3209
3210    rsp->status = strdup("302 Local Redirect from Junkbuster");
3211    if (rsp->status == NULL)
3212    {
3213       free(target);
3214       return JB_ERR_MEMORY;
3215    }
3216    err = enlist_unique_header(rsp->headers, "Location", target);
3217    free(target);
3218
3219    return err;
3220 }
3221
3222
3223 /*********************************************************************
3224  *
3225  * Function    :  cgi_edit_actions_section_remove
3226  *
3227  * Description :  CGI function that actually removes a whole section from
3228  *                the actions file.  The section must be empty first
3229  *                (else JB_ERR_CGI_PARAMS).
3230  *
3231  * Parameters  :
3232  *           1 :  csp = Current client state (buffers, headers, etc...)
3233  *           2 :  rsp = http_response data structure for output
3234  *           3 :  parameters = map of cgi parameters
3235  *
3236  * CGI Parameters :
3237  *    filename : Identifies the file to edit
3238  *         ver : File's last-modified time
3239  *     section : Line number of section to edit
3240  *     pattern : Line number of pattern to edit
3241  *
3242  * Returns     :  JB_ERR_OK     on success
3243  *                JB_ERR_MEMORY on out-of-memory
3244  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3245  *                                  specified or not valid.
3246  *
3247  *********************************************************************/
3248 jb_err cgi_edit_actions_section_remove(struct client_state *csp,
3249                                        struct http_response *rsp,
3250                                        const struct map *parameters)
3251 {
3252    unsigned sectionid;
3253    struct editable_file * file;
3254    struct file_line * cur_line;
3255    struct file_line * prev_line;
3256    unsigned line_number;
3257    char * target;
3258    jb_err err;
3259
3260    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3261    {
3262       return cgi_error_disabled(csp, rsp);
3263    }
3264
3265    err = get_number_param(csp, parameters, "section", &sectionid);
3266    if (err)
3267    {
3268       return err;
3269    }
3270
3271    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3272    if (err)
3273    {
3274       /* No filename specified, can't read file, modified, or out of memory. */
3275       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3276    }
3277
3278    line_number = 1;
3279    cur_line = file->lines;
3280
3281    prev_line = NULL;
3282    while ((cur_line != NULL) && (line_number < sectionid))
3283    {
3284       prev_line = cur_line;
3285       cur_line = cur_line->next;
3286       line_number++;
3287    }
3288
3289    if ( (cur_line == NULL)
3290      || (cur_line->type != FILE_LINE_ACTION) )
3291    {
3292       /* Invalid "sectionid" parameter */
3293       edit_free_file(file);
3294       return JB_ERR_CGI_PARAMS;
3295    }
3296
3297    if ( (cur_line->next != NULL)
3298      && (cur_line->next->type == FILE_LINE_URL) )
3299    {
3300       /* Section not empty. */
3301       edit_free_file(file);
3302       return JB_ERR_CGI_PARAMS;
3303    }
3304
3305    /* At this point, the line to remove is in cur_line, and the previous
3306     * one is in prev_line
3307     */
3308
3309    /* Unlink cur_line */
3310    if (prev_line == NULL)
3311    {
3312       /* Removing the first line from the file */
3313       file->lines = cur_line->next;
3314    }
3315    else
3316    {
3317       prev_line->next = cur_line->next;
3318    }
3319    cur_line->next = NULL;
3320
3321    /* Free cur_line */
3322    edit_free_file_lines(cur_line);
3323
3324    err = edit_write_file(file);
3325    if (err)
3326    {
3327       /* Error writing file */
3328       edit_free_file(file);
3329       return err;
3330    }
3331
3332    target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
3333    string_append(&target, file->identifier);
3334
3335    edit_free_file(file);
3336
3337    if (target == NULL)
3338    {
3339       /* Out of memory */
3340       return JB_ERR_MEMORY;
3341    }
3342
3343    rsp->status = strdup("302 Local Redirect from Junkbuster");
3344    if (rsp->status == NULL)
3345    {
3346       free(target);
3347       return JB_ERR_MEMORY;
3348    }
3349    err = enlist_unique_header(rsp->headers, "Location", target);
3350    free(target);
3351
3352    return err;
3353 }
3354
3355
3356 /*********************************************************************
3357  *
3358  * Function    :  cgi_edit_actions_section_add
3359  *
3360  * Description :  CGI function that adds a new empty section to
3361  *                an actions file.
3362  *
3363  * Parameters  :
3364  *           1 :  csp = Current client state (buffers, headers, etc...)
3365  *           2 :  rsp = http_response data structure for output
3366  *           3 :  parameters = map of cgi parameters
3367  *
3368  * CGI Parameters :
3369  *    filename : Identifies the file to edit
3370  *         ver : File's last-modified time
3371  *     section : Line number of section to add after, 0 for start
3372  *               of file.
3373  *
3374  * Returns     :  JB_ERR_OK     on success
3375  *                JB_ERR_MEMORY on out-of-memory
3376  *                JB_ERR_CGI_PARAMS if the CGI parameters are not
3377  *                                  specified or not valid.
3378  *
3379  *********************************************************************/
3380 jb_err cgi_edit_actions_section_add(struct client_state *csp,
3381                                     struct http_response *rsp,
3382                                     const struct map *parameters)
3383 {
3384    unsigned sectionid;
3385    struct file_line * new_line;
3386    char * new_text;
3387    struct editable_file * file;
3388    struct file_line * cur_line;
3389    unsigned line_number;
3390    char * target;
3391    jb_err err;
3392
3393    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3394    {
3395       return cgi_error_disabled(csp, rsp);
3396    }
3397
3398    err = get_number_param(csp, parameters, "section", &sectionid);
3399    if (err)
3400    {
3401       return err;
3402    }
3403
3404    err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3405    if (err)
3406    {
3407       /* No filename specified, can't read file, modified, or out of memory. */
3408       return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3409    }
3410
3411    line_number = 1;
3412    cur_line = file->lines;
3413
3414    if (sectionid < 1U)
3415    {
3416       /* Add to start of file */
3417       if (cur_line != NULL)
3418       {
3419          /* There's something in the file, find the line before the first
3420           * action.
3421           */
3422          while ( (cur_line->next != NULL)
3423               && (cur_line->next->type != FILE_LINE_ACTION) )
3424          {
3425             cur_line = cur_line->next;
3426             line_number++;
3427          }
3428       }
3429    }
3430    else
3431    {
3432       /* Add after stated section. */
3433       while ((cur_line != NULL) && (line_number < sectionid))
3434       {
3435          cur_line = cur_line->next;
3436          line_number++;
3437       }
3438
3439       if ( (cur_line == NULL)
3440         || (cur_line->type != FILE_LINE_ACTION))
3441       {
3442          /* Invalid "sectionid" parameter */
3443          edit_free_file(file);
3444          return JB_ERR_CGI_PARAMS;
3445       }
3446
3447       /* Skip through the section to find the last line in it. */
3448       while ( (cur_line->next != NULL)
3449            && (cur_line->next->type != FILE_LINE_ACTION) )
3450       {
3451          cur_line = cur_line->next;
3452          line_number++;
3453       }
3454    }
3455
3456    /* At this point, the last line in the previous section is in cur_line
3457     * - add after this.  (Or if we need to add as the first line, cur_line
3458     * will be NULL).
3459     */
3460
3461    new_text = strdup("{}");
3462    if (NULL == new_text)
3463    {
3464       edit_free_file(file);
3465       return JB_ERR_MEMORY;
3466    }
3467
3468    /* Allocate the new line */
3469    new_line = (struct file_line *)zalloc(sizeof(*new_line));
3470    if (new_line == NULL)
3471    {
3472       free(new_text);
3473       edit_free_file(file);
3474       return JB_ERR_MEMORY;
3475    }
3476
3477    /* Fill in the data members of the new line */
3478    new_line->raw = NULL;
3479    new_line->prefix = NULL;
3480    new_line->unprocessed = new_text;
3481    new_line->type = FILE_LINE_ACTION;
3482
3483    if (cur_line != NULL)
3484    {
3485       /* Link new_line into the list, after cur_line */
3486       new_line->next = cur_line->next;
3487       cur_line->next = new_line;
3488    }
3489    else
3490    {
3491       /* Link new_line into the list, as first line */
3492       new_line->next = file->lines;
3493       file->lines = new_line;
3494    }
3495
3496    /* Done making changes, now commit */
3497
3498    err = edit_write_file(file);
3499    if (err)
3500    {
3501       /* Error writing file */
3502       edit_free_file(file);
3503       return err;
3504    }
3505
3506    target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
3507    string_append(&target, file->identifier);
3508
3509    edit_free_file(file);
3510
3511    if (target == NULL)
3512    {
3513       /* Out of memory */
3514       return JB_ERR_MEMORY;
3515    }
3516
3517    rsp->status = strdup("302 Local Redirect from Junkbuster");
3518    if (rsp->status == NULL)
3519    {
3520       free(target);
3521       return JB_ERR_MEMORY;
3522    }
3523    err = enlist_unique_header(rsp->headers, "Location", target);
3524    free(target);
3525
3526    return err;
3527 }
3528
3529
3530 /*********************************************************************
3531  *
3532  * Function    :  cgi_toggle
3533  *
3534  * Description :  CGI function that adds a new empty section to
3535  *                an actions file.
3536  *
3537  * Parameters  :
3538  *           1 :  csp = Current client state (buffers, headers, etc...)
3539  *           2 :  rsp = http_response data structure for output
3540  *           3 :  parameters = map of cgi parameters
3541  *
3542  * CGI Parameters :
3543  *         set : If present, how to change toggle setting:
3544  *               "enable", "disable", "toggle", or none (default).
3545  *        mini : If present, use mini reply template.
3546  *
3547  * Returns     :  JB_ERR_OK     on success
3548  *                JB_ERR_MEMORY on out-of-memory
3549  *
3550  *********************************************************************/
3551 jb_err cgi_toggle(struct client_state *csp,
3552                   struct http_response *rsp,
3553                   const struct map *parameters)
3554 {
3555    struct map *exports;
3556    char mode;
3557    const char *template_name;
3558    jb_err err;
3559
3560    assert(csp);
3561    assert(rsp);
3562    assert(parameters);
3563
3564    if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_TOGGLE))
3565    {
3566       return cgi_error_disabled(csp, rsp);
3567    }
3568
3569    if (NULL == (exports = default_exports(csp, "toggle")))
3570    {
3571       return JB_ERR_MEMORY;
3572    }
3573
3574    mode = *(lookup(parameters, "set"));
3575
3576    if (mode == 'e')
3577    {
3578       /* Enable */
3579       g_bToggleIJB = 1;
3580    }
3581    else if (mode == 'd')
3582    {
3583       /* Disable */
3584       g_bToggleIJB = 0;
3585    }
3586    else if (mode == 't')
3587    {
3588       /* Toggle */
3589       g_bToggleIJB = !g_bToggleIJB;
3590    }
3591
3592    err = map_conditional(exports, "enabled", g_bToggleIJB);
3593    if (err)
3594    {
3595       free_map(exports);
3596       return err;
3597    }
3598
3599    template_name = (*(lookup(parameters, "mini"))
3600                  ? "toggle-mini"
3601                  : "toggle");
3602
3603    return template_fill_for_cgi(csp, template_name, exports, rsp);
3604 }
3605 #endif /* def FEATURE_CGI_EDIT_ACTIONS */
3606
3607
3608 /*
3609   Local Variables:
3610   tab-width: 3
3611   end:
3612 */