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