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