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