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