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