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