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