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