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